aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorshedaniel <daniel@shedaniel.me>2024-09-17 13:47:41 +0800
committershedaniel <daniel@shedaniel.me>2024-09-17 13:50:51 +0800
commit27e06fad89867281ef7d7bcebff194633812c9da (patch)
tree03d1830628e7b46b7f94e01ce72cf954e2a2722f
parent7b5ed400a9bb5a26013441a7ca5589ca30803abd (diff)
parentb8d0975527d86b79a1f9c160415baf8a6e6cf5cb (diff)
downloadRoughlyEnoughItems-27e06fad89867281ef7d7bcebff194633812c9da.tar.gz
RoughlyEnoughItems-27e06fad89867281ef7d7bcebff194633812c9da.tar.bz2
RoughlyEnoughItems-27e06fad89867281ef7d7bcebff194633812c9da.zip
Merge remote-tracking branch 'origin/9.x-1.19' into 11.x-1.19.4
# Conflicts: # fabric/src/main/resources/roughlyenoughitems.accessWidener # forge/src/main/resources/META-INF/accesstransformer.cfg
-rw-r--r--api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Slot.java6
-rw-r--r--api/src/main/java/me/shedaniel/rei/api/common/plugins/PluginView.java21
-rw-r--r--api/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginReloadContext.java48
-rw-r--r--api/src/main/java/me/shedaniel/rei/impl/common/plugins/ReloadInterruptionContext.java63
-rw-r--r--fabric/src/main/resources/roughlyenoughitems.accessWidener4
-rw-r--r--forge/src/main/resources/META-INF/accesstransformer.cfg4
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java32
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java50
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigGroups.java3
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigOptions.java6
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/DisplayRegistryInfoScreen.java174
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/PerformanceScreen.java38
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/SubCategoryListEntry.java4
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ScreenWithMenu.java89
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java7
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayCache.java4
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayCacheImpl.java16
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java78
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/util/OriginalRetainingCyclingList.java42
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java8
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java187
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/ReloadManagerImpl.java211
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/common/registry/RecipeManagerContextImpl.java17
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/common/util/InstanceHelper.java125
-rwxr-xr-xruntime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json6
25 files changed, 1026 insertions, 217 deletions
diff --git a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Slot.java b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Slot.java
index 02527dafd..c968e685f 100644
--- a/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Slot.java
+++ b/api/src/main/java/me/shedaniel/rei/api/client/gui/widgets/Slot.java
@@ -25,10 +25,12 @@ package me.shedaniel.rei.api.client.gui.widgets;
import me.shedaniel.math.Rectangle;
import me.shedaniel.rei.api.common.entry.EntryStack;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
+import java.util.function.Consumer;
public abstract class Slot extends WidgetWithBounds {
public static final byte UN_MARKED = 0;
@@ -133,6 +135,10 @@ public abstract class Slot extends WidgetWithBounds {
public abstract Slot entries(Collection<? extends EntryStack<?>> stacks);
+ @ApiStatus.Experimental
+ @ApiStatus.Internal
+ public abstract Slot withEntriesListener(Consumer<Slot> listener);
+
public abstract EntryStack<?> getCurrentEntry();
public abstract List<EntryStack<?>> getEntries();
diff --git a/api/src/main/java/me/shedaniel/rei/api/common/plugins/PluginView.java b/api/src/main/java/me/shedaniel/rei/api/common/plugins/PluginView.java
index b1072cb21..0e9918c66 100644
--- a/api/src/main/java/me/shedaniel/rei/api/common/plugins/PluginView.java
+++ b/api/src/main/java/me/shedaniel/rei/api/common/plugins/PluginView.java
@@ -24,9 +24,9 @@
package me.shedaniel.rei.api.common.plugins;
import me.shedaniel.rei.api.client.plugins.REIClientPlugin;
-import me.shedaniel.rei.api.common.registry.ReloadStage;
import me.shedaniel.rei.impl.ClientInternals;
import me.shedaniel.rei.impl.Internals;
+import me.shedaniel.rei.impl.common.plugins.PluginReloadContext;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import org.jetbrains.annotations.ApiStatus;
@@ -62,18 +62,25 @@ public interface PluginView<P extends REIPlugin<?>> {
}
@Override
- public void pre(ReloadStage stage) {
- PluginView.this.pre(stage);
+ public void pre(PluginReloadContext context) throws InterruptedException {
+ PluginView.this.pre(context);
}
@Override
- public void post(ReloadStage stage) {
- PluginView.this.post(stage);
+ public void reload(PluginReloadContext context) throws InterruptedException {
+ PluginView.this.reload(context);
+ }
+
+ @Override
+ public void post(PluginReloadContext context) throws InterruptedException {
+ PluginView.this.post(context);
}
};
}
- void pre(ReloadStage stage);
+ void pre(PluginReloadContext context) throws InterruptedException;
+
+ void reload(PluginReloadContext context) throws InterruptedException;
- void post(ReloadStage stage);
+ void post(PluginReloadContext context) throws InterruptedException;
}
diff --git a/api/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginReloadContext.java b/api/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginReloadContext.java
new file mode 100644
index 000000000..0481b38d0
--- /dev/null
+++ b/api/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginReloadContext.java
@@ -0,0 +1,48 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.common.plugins;
+
+import me.shedaniel.rei.api.common.registry.ReloadStage;
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+public interface PluginReloadContext {
+ ReloadStage stage();
+
+ ReloadInterruptionContext interruptionContext();
+
+ static PluginReloadContext of(ReloadStage stage, ReloadInterruptionContext interruptionContext) {
+ return new PluginReloadContext() {
+ @Override
+ public ReloadStage stage() {
+ return stage;
+ }
+
+ @Override
+ public ReloadInterruptionContext interruptionContext() {
+ return interruptionContext;
+ }
+ };
+ }
+}
diff --git a/api/src/main/java/me/shedaniel/rei/impl/common/plugins/ReloadInterruptionContext.java b/api/src/main/java/me/shedaniel/rei/impl/common/plugins/ReloadInterruptionContext.java
new file mode 100644
index 000000000..19201b20b
--- /dev/null
+++ b/api/src/main/java/me/shedaniel/rei/impl/common/plugins/ReloadInterruptionContext.java
@@ -0,0 +1,63 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.common.plugins;
+
+import me.shedaniel.rei.impl.common.InternalLogger;
+import org.jetbrains.annotations.ApiStatus;
+
+@FunctionalInterface
+@ApiStatus.Internal
+public interface ReloadInterruptionContext {
+ boolean isInterrupted();
+
+ default void checkInterrupted() throws InterruptedException {
+ if (isInterrupted()) {
+ InternalLogger.getInstance().debug("Plugin reload interrupted!");
+ throw new InterruptedException();
+ }
+ }
+
+ default ReloadInterruptionContext withJob(Runnable ifInterrupted) {
+ return new ReloadInterruptionContext() {
+ @Override
+ public boolean isInterrupted() {
+ return ReloadInterruptionContext.this.isInterrupted();
+ }
+
+ @Override
+ public void checkInterrupted() throws InterruptedException {
+ try {
+ ReloadInterruptionContext.this.checkInterrupted();
+ } catch (InterruptedException e) {
+ ifInterrupted.run();
+ throw e;
+ }
+ }
+ };
+ }
+
+ static ReloadInterruptionContext ofNever() {
+ return () -> false;
+ }
+}
diff --git a/fabric/src/main/resources/roughlyenoughitems.accessWidener b/fabric/src/main/resources/roughlyenoughitems.accessWidener
index 22dc526c1..04096fc65 100644
--- a/fabric/src/main/resources/roughlyenoughitems.accessWidener
+++ b/fabric/src/main/resources/roughlyenoughitems.accessWidener
@@ -42,4 +42,6 @@ accessible field net/minecraft/world/item/crafting/SmithingTransformRecipe base
accessible field net/minecraft/world/item/crafting/SmithingTransformRecipe addition Lnet/minecraft/world/item/crafting/Ingredient;
accessible field net/minecraft/world/item/crafting/SmithingTrimRecipe template Lnet/minecraft/world/item/crafting/Ingredient;
accessible field net/minecraft/world/item/crafting/SmithingTrimRecipe base Lnet/minecraft/world/item/crafting/Ingredient;
-accessible field net/minecraft/world/item/crafting/SmithingTrimRecipe addition Lnet/minecraft/world/item/crafting/Ingredient; \ No newline at end of file
+accessible field net/minecraft/world/item/crafting/SmithingTrimRecipe addition Lnet/minecraft/world/item/crafting/Ingredient;
+accessible field net/minecraft/client/multiplayer/ClientLevel connection Lnet/minecraft/client/multiplayer/ClientPacketListener;
+accessible field net/minecraft/client/multiplayer/MultiPlayerGameMode connection Lnet/minecraft/client/multiplayer/ClientPacketListener; \ No newline at end of file
diff --git a/forge/src/main/resources/META-INF/accesstransformer.cfg b/forge/src/main/resources/META-INF/accesstransformer.cfg
index cd8fd6891..a974e1d09 100644
--- a/forge/src/main/resources/META-INF/accesstransformer.cfg
+++ b/forge/src/main/resources/META-INF/accesstransformer.cfg
@@ -43,4 +43,6 @@ public net.minecraft.world.item.crafting.SmithingTransformRecipe f_265888_ # bas
public net.minecraft.world.item.crafting.SmithingTransformRecipe f_265907_ # addition
public net.minecraft.world.item.crafting.SmithingTrimRecipe f_265958_ # template
public net.minecraft.world.item.crafting.SmithingTrimRecipe f_266040_ # base
-public net.minecraft.world.item.crafting.SmithingTrimRecipe f_266053_ # addition \ No newline at end of file
+public net.minecraft.world.item.crafting.SmithingTrimRecipe f_266053_ # addition
+public net.minecraft.client.multiplayer.ClientLevel f_104561_ # connection
+public net.minecraft.client.multiplayer.MultiPlayerGameMode f_105190_ # connection \ No newline at end of file
diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java
index 732ca00cd..3f99fa7cb 100644
--- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java
+++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java
@@ -29,13 +29,10 @@ import dev.architectury.platform.Platform;
import dev.architectury.registry.ReloadListenerRegistry;
import dev.architectury.utils.Env;
import dev.architectury.utils.EnvExecutor;
-import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.annotation.Nullable;
import me.shedaniel.rei.api.common.entry.type.EntryType;
-import me.shedaniel.rei.api.common.plugins.PluginManager;
import me.shedaniel.rei.api.common.plugins.PluginView;
import me.shedaniel.rei.api.common.plugins.REIPlugin;
import me.shedaniel.rei.api.common.plugins.REIServerPlugin;
-import me.shedaniel.rei.api.common.registry.ReloadStage;
import me.shedaniel.rei.impl.Internals;
import me.shedaniel.rei.impl.common.InternalLogger;
import me.shedaniel.rei.impl.common.category.CategoryIdentifierImpl;
@@ -53,6 +50,8 @@ import me.shedaniel.rei.impl.common.logging.*;
import me.shedaniel.rei.impl.common.logging.performance.PerformanceLogger;
import me.shedaniel.rei.impl.common.logging.performance.PerformanceLoggerImpl;
import me.shedaniel.rei.impl.common.plugins.PluginManagerImpl;
+import me.shedaniel.rei.impl.common.plugins.ReloadInterruptionContext;
+import me.shedaniel.rei.impl.common.plugins.ReloadManagerImpl;
import me.shedaniel.rei.impl.common.registry.RecipeManagerContextImpl;
import me.shedaniel.rei.impl.common.transfer.MenuInfoRegistryImpl;
import me.shedaniel.rei.impl.common.transfer.SlotAccessorRegistryImpl;
@@ -135,7 +134,7 @@ public class RoughlyEnoughItemsCore {
UnaryOperator.identity(),
new EntryTypeRegistryImpl(),
new EntrySettingsAdapterRegistryImpl(),
- new RecipeManagerContextImpl<>(RecipeManagerContextImpl.supplier()),
+ new RecipeManagerContextImpl<>(),
new ItemComparatorRegistryImpl(),
new FluidComparatorRegistryImpl(),
new DisplaySerializerRegistryImpl(),
@@ -147,28 +146,6 @@ public class RoughlyEnoughItemsCore {
new SlotAccessorRegistryImpl()), "serverPluginManager");
}
- public static void _reloadPlugins(@Nullable ReloadStage stage) {
- if (stage == null) {
- for (ReloadStage reloadStage : ReloadStage.values()) {
- _reloadPlugins(reloadStage);
- }
- return;
- }
- try {
- for (PluginManager<? extends REIPlugin<?>> instance : PluginManager.getActiveInstances()) {
- instance.view().pre(stage);
- }
- for (PluginManager<? extends REIPlugin<?>> instance : PluginManager.getActiveInstances()) {
- instance.startReload(stage);
- }
- for (PluginManager<? extends REIPlugin<?>> instance : PluginManager.getActiveInstances()) {
- instance.view().post(stage);
- }
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- }
- }
-
public void onInitialize() {
PluginDetector detector = getPluginDetector();
detector.detectCommonPlugins();
@@ -179,8 +156,7 @@ public class RoughlyEnoughItemsCore {
MutableLong lastReload = new MutableLong(-1);
ReloadListenerRegistry.register(PackType.SERVER_DATA, (preparationBarrier, resourceManager, profilerFiller, profilerFiller2, executor, executor2) -> {
return preparationBarrier.wait(Unit.INSTANCE).thenRunAsync(() -> {
- PERFORMANCE_LOGGER.clear();
- RoughlyEnoughItemsCore._reloadPlugins(null);
+ ReloadManagerImpl.reloadPlugins(null, ReloadInterruptionContext.ofNever());
}, executor2);
});
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java
index de49c9a29..576193baf 100644
--- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java
+++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java
@@ -24,13 +24,12 @@
package me.shedaniel.rei;
import com.google.common.collect.Lists;
-import com.mojang.blaze3d.systems.RenderSystem;
-import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.serialization.DataResult;
import dev.architectury.event.Event;
import dev.architectury.event.EventFactory;
import dev.architectury.event.EventResult;
import dev.architectury.event.events.client.ClientGuiEvent;
+import dev.architectury.event.events.client.ClientPlayerEvent;
import dev.architectury.event.events.client.ClientRecipeUpdateEvent;
import dev.architectury.event.events.client.ClientScreenInputEvent;
import dev.architectury.networking.NetworkManager;
@@ -67,7 +66,6 @@ import me.shedaniel.rei.impl.client.entry.filtering.rules.FilteringRuleTypeRegis
import me.shedaniel.rei.impl.client.entry.renderer.EntryRendererRegistryImpl;
import me.shedaniel.rei.impl.client.favorites.DelegatingFavoriteEntryProviderImpl;
import me.shedaniel.rei.impl.client.favorites.FavoriteEntryTypeRegistryImpl;
-import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl;
import me.shedaniel.rei.impl.client.gui.modules.entries.SubMenuEntry;
import me.shedaniel.rei.impl.client.gui.modules.entries.ToggleMenuEntry;
import me.shedaniel.rei.impl.client.gui.widget.InternalWidgets;
@@ -89,6 +87,8 @@ import me.shedaniel.rei.impl.common.entry.type.EntryRegistryImpl;
import me.shedaniel.rei.impl.common.entry.type.collapsed.CollapsibleEntryRegistryImpl;
import me.shedaniel.rei.impl.common.entry.type.types.EmptyEntryDefinition;
import me.shedaniel.rei.impl.common.plugins.PluginManagerImpl;
+import me.shedaniel.rei.impl.common.plugins.ReloadManagerImpl;
+import me.shedaniel.rei.impl.common.util.InstanceHelper;
import me.shedaniel.rei.impl.common.util.IssuesDetector;
import me.shedaniel.rei.plugin.test.REITestPlugin;
import net.fabricmc.api.EnvType;
@@ -121,7 +121,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.List;
-import java.util.concurrent.*;
import java.util.function.BiFunction;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
@@ -133,15 +132,6 @@ public class RoughlyEnoughItemsCoreClient {
public static final Event<ClientRecipeUpdateEvent> PRE_UPDATE_RECIPES = EventFactory.createLoop();
public static final Event<Runnable> POST_UPDATE_TAGS = EventFactory.createLoop();
public static boolean isLeftMousePressed = false;
- private static final ExecutorService RELOAD_PLUGINS = Executors.newSingleThreadScheduledExecutor(task -> {
- Thread thread = new Thread(task, "REI-ReloadPlugins");
- thread.setDaemon(true);
- thread.setUncaughtExceptionHandler(($, exception) -> {
- InternalLogger.getInstance().throwException(exception);
- });
- return thread;
- });
- private static final List<Future<?>> RELOAD_TASKS = new CopyOnWriteArrayList<>();
public static void attachClientInternals() {
InternalWidgets.attach();
@@ -317,24 +307,25 @@ public class RoughlyEnoughItemsCoreClient {
private void registerEvents() {
Minecraft client = Minecraft.getInstance();
final ResourceLocation recipeButtonTex = new ResourceLocation("textures/gui/recipe_button.png");
- MutableLong startReload = new MutableLong(-1);
MutableLong endReload = new MutableLong(-1);
PRE_UPDATE_RECIPES.register(recipeManager -> {
- RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.clear();
- reloadPlugins(startReload, ReloadStage.START);
+ reloadPlugins(null, ReloadStage.START);
});
ClientRecipeUpdateEvent.EVENT.register(recipeManager -> {
reloadPlugins(endReload, ReloadStage.END);
});
+ ClientPlayerEvent.CLIENT_PLAYER_QUIT.register(player -> {
+ InternalLogger.getInstance().debug("Player quit, clearing reload tasks!");
+ endReload.setValue(-1);
+ ReloadManagerImpl.terminateReloadTasks();
+ });
ClientGuiEvent.INIT_PRE.register((screen, access) -> {
List<ReloadStage> stages = ((PluginManagerImpl<REIPlugin<?>>) PluginManager.getInstance()).getObservedStages();
if (Minecraft.getInstance().level != null && Minecraft.getInstance().player != null && stages.contains(ReloadStage.START)
&& !stages.contains(ReloadStage.END) && !PluginManager.areAnyReloading() && screen instanceof AbstractContainerScreen) {
- for (Future<?> task : RELOAD_TASKS) {
- if (!task.isDone()) {
- return EventResult.pass();
- }
+ if (ReloadManagerImpl.countRunningReloadTasks() > 0) {
+ return EventResult.pass();
}
InternalLogger.getInstance().error("Detected missing stage: END! This is possibly due to issues during client recipe reload! REI will force a reload of the recipes now!");
@@ -473,27 +464,12 @@ public class RoughlyEnoughItemsCoreClient {
public static void reloadPlugins(MutableLong lastReload, @Nullable ReloadStage start) {
if (Minecraft.getInstance().level == null) return;
if (lastReload != null) {
- if (lastReload.getValue() > 0 && System.currentTimeMillis() - lastReload.getValue() <= 5000) {
+ if (lastReload.getValue() > 0 && System.currentTimeMillis() - lastReload.getValue() <= 1000) {
InternalLogger.getInstance().warn("Suppressing Reload Plugins of stage " + start);
return;
}
lastReload.setValue(System.currentTimeMillis());
}
- InternalLogger.getInstance().debug("Starting Reload Plugins of stage " + start, new Throwable());
- if (ConfigObject.getInstance().doesRegisterRecipesInAnotherThread()) {
- Future<?>[] futures = new Future<?>[1];
- CompletableFuture<Void> future = CompletableFuture.runAsync(() -> RoughlyEnoughItemsCore._reloadPlugins(start), RELOAD_PLUGINS)
- .whenComplete((unused, throwable) -> {
- // Remove the future from the list of futures
- if (futures[0] != null) {
- RELOAD_TASKS.remove(futures[0]);
- futures[0] = null;
- }
- });
- futures[0] = future;
- RELOAD_TASKS.add(future);
- } else {
- RoughlyEnoughItemsCore._reloadPlugins(start);
- }
+ ReloadManagerImpl.reloadPlugins(start, () -> InstanceHelper.connectionFromClient() == null);
}
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigGroups.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigGroups.java
index 28b83e639..b206fd6e5 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigGroups.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigGroups.java
@@ -116,7 +116,8 @@ public interface AllREIConfigGroups {
OptionGroup DEBUG_PERFORMANCE = make("debug.performance")
.add(PLUGINS_PERFORMANCE)
.add(SEARCH_PERFORMANCE)
- .add(ENTRY_LIST_PERFORMANCE);
+ .add(ENTRY_LIST_PERFORMANCE)
+ .add(DISPLAY_REGISTRY_ANALYSIS);
OptionGroup RESET_RELOAD = make("reset.reload")
.add(RELOAD_PLUGINS)
.add(RELOAD_SEARCH);
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigOptions.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigOptions.java
index 30340bfba..97119df2e 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigOptions.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigOptions.java
@@ -24,7 +24,6 @@
package me.shedaniel.rei.impl.client.gui.config.options;
import me.shedaniel.clothconfig2.api.ModifierKeyCode;
-import me.shedaniel.rei.RoughlyEnoughItemsCore;
import me.shedaniel.rei.RoughlyEnoughItemsCoreClient;
import me.shedaniel.rei.api.client.config.entry.EntryStackProvider;
import me.shedaniel.rei.api.client.gui.config.*;
@@ -37,6 +36,7 @@ import me.shedaniel.rei.impl.client.config.entries.ConfigureCategoriesScreen;
import me.shedaniel.rei.impl.client.config.entries.FilteringEntry;
import me.shedaniel.rei.impl.client.gui.config.REIConfigScreen;
import me.shedaniel.rei.impl.client.gui.config.options.configure.PanelBoundariesConfiguration;
+import me.shedaniel.rei.impl.client.gui.performance.DisplayRegistryInfoScreen;
import me.shedaniel.rei.impl.client.gui.performance.PerformanceScreen;
import me.shedaniel.rei.impl.client.gui.screen.ConfigReloadingScreen;
import me.shedaniel.rei.impl.client.gui.screen.collapsible.CollapsibleEntriesScreen;
@@ -246,9 +246,11 @@ public interface AllREIConfigOptions {
.enabledDisabled();
CompositeOption<Boolean> ENTRY_LIST_PERFORMANCE = make("debug.entry_list_performance", i -> i.advanced.layout.debugRenderTimeRequired, (i, v) -> i.advanced.layout.debugRenderTimeRequired = v)
.enabledDisabled();
+ CompositeOption<Object> DISPLAY_REGISTRY_ANALYSIS = make("debug.display_registry_analysis", i -> null, (i, v) -> new Object())
+ .details((access, option, onClose) -> Minecraft.getInstance().setScreen(new DisplayRegistryInfoScreen(onClose)))
+ .requiresLevel();
CompositeOption<Object> RELOAD_PLUGINS = make("reset.reload_plugins", i -> null, (i, v) -> new Object())
.reload((access, option, onClose) -> {
- RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.clear();
RoughlyEnoughItemsCoreClient.reloadPlugins(null, null);
while (!PluginManager.areAnyReloading()) {
try {
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/DisplayRegistryInfoScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/DisplayRegistryInfoScreen.java
new file mode 100644
index 000000000..fd4422232
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/DisplayRegistryInfoScreen.java
@@ -0,0 +1,174 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.client.gui.performance;
+
+import com.mojang.blaze3d.systems.RenderSystem;
+import com.mojang.blaze3d.vertex.PoseStack;
+import me.shedaniel.clothconfig2.gui.widget.DynamicElementListWidget;
+import me.shedaniel.math.Rectangle;
+import me.shedaniel.rei.api.client.registry.display.DisplayRegistry;
+import me.shedaniel.rei.api.common.category.CategoryIdentifier;
+import me.shedaniel.rei.api.common.display.Display;
+import me.shedaniel.rei.api.common.util.CollectionUtils;
+import me.shedaniel.rei.impl.client.gui.modules.Menu;
+import me.shedaniel.rei.impl.client.gui.modules.entries.ToggleMenuEntry;
+import me.shedaniel.rei.impl.client.gui.screen.ScreenWithMenu;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiComponent;
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.gui.narration.NarratableEntry;
+import net.minecraft.network.chat.Component;
+import net.minecraft.util.FormattedCharSequence;
+
+import java.util.*;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+@Environment(EnvType.CLIENT)
+public class DisplayRegistryInfoScreen extends ScreenWithMenu {
+ private Runnable onClose;
+
+ public DisplayRegistryInfoScreen(Runnable onClose) {
+ super(Component.translatable("text.rei.display_registry_analysis"));
+ this.onClose = onClose;
+ }
+
+ private ListWidget list;
+ private SortType sortType = SortType.ID;
+
+ @Override
+ public void init() {
+ {
+ Component backText = Component.literal("↩ ").append(Component.translatable("gui.back"));
+ addRenderableWidget(new Button(4, 4, Minecraft.getInstance().font.width(backText) + 10, 20, backText, button -> {
+ this.onClose.run();
+ this.onClose = null;
+ }, Supplier::get) {});
+ }
+ {
+ Component text = Component.translatable("text.rei.sort");
+ Rectangle bounds = new Rectangle(this.width - 4 - Minecraft.getInstance().font.width(text) - 10, 4, Minecraft.getInstance().font.width(text) + 10, 20);
+ addRenderableWidget(new Button(bounds.x, bounds.y, bounds.width, bounds.height, text, button -> {
+ this.setMenu(new Menu(bounds, CollectionUtils.map(SortType.values(), type -> {
+ return ToggleMenuEntry.of(Component.translatable("text.rei.sort.by", type.name().toLowerCase(Locale.ROOT)), () -> false, o -> {
+ this.closeMenu();
+ this.sortType = type;
+ this.init(this.minecraft, this.width, this.height);
+ });
+ }), false));
+ }, Supplier::get) {});
+ }
+ list = new ListWidget();
+ list.addItem(new EntryImpl(Component.literal("Total Displays"), DisplayRegistry.getInstance().displaySize()));
+ sort(DisplayRegistry.getInstance().getAll().entrySet().stream())
+ .forEach(entry -> {
+ list.addItem(new EntryImpl(entry.getKey(), entry.getValue().size()));
+ });
+ addWidget(list);
+ }
+
+ private Stream<Map.Entry<CategoryIdentifier<?>, List<Display>>> sort(Stream<Map.Entry<CategoryIdentifier<?>, List<Display>>> stream) {
+ return switch (sortType) {
+ case COUNT -> stream.sorted(Comparator.<Map.Entry<CategoryIdentifier<?>, List<Display>>>comparingInt(value -> value.getValue().size()).reversed());
+ case ID -> stream.sorted(Comparator.comparing(value -> value.getKey().toString()));
+ };
+ }
+
+ @Override
+ public void render(PoseStack poses, int mouseX, int mouseY, float delta) {
+ renderDirtBackground(poses);
+ list.render(poses, mouseX, mouseY, delta);
+ this.font.drawShadow(poses, this.title.getVisualOrderText(), this.width / 2.0F - this.font.width(this.title) / 2.0F, 12.0F, -1);
+ super.render(poses, mouseX, mouseY, delta);
+ }
+
+ public static abstract class ListEntry extends DynamicElementListWidget.ElementEntry<ListEntry> {
+ }
+
+ private class ListWidget extends DynamicElementListWidget<ListEntry> {
+ public ListWidget() {
+ super(DisplayRegistryInfoScreen.this.minecraft, DisplayRegistryInfoScreen.this.width, DisplayRegistryInfoScreen.this.height, 30, DisplayRegistryInfoScreen.this.height, GuiComponent.BACKGROUND_LOCATION);
+ }
+
+ @Override
+ public int getItemWidth() {
+ return width;
+ }
+
+ @Override
+ public int addItem(ListEntry item) {
+ return super.addItem(item);
+ }
+
+ @Override
+ protected int getScrollbarPosition() {
+ return width - 6;
+ }
+ }
+
+ public static class EntryImpl extends ListEntry {
+ private final Component component;
+ public final int count;
+
+ public EntryImpl(CategoryIdentifier<?> identifier, int count) {
+ this(Component.literal(identifier.getIdentifier().toString()), count);
+ }
+
+ public EntryImpl(Component component, int count) {
+ this.component = component;
+ this.count = count;
+ }
+
+ @Override
+ public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) {
+ RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
+ Minecraft.getInstance().font.drawShadow(matrices, this.component.getVisualOrderText(), (float) x + 4, (float) (y + 6), -1);
+ FormattedCharSequence rightText = Component.translatable("text.rei.display_registry_analysis.displays", count).getVisualOrderText();
+ Minecraft.getInstance().font.drawShadow(matrices, rightText, (float) x + entryWidth - 6 - 8 - Minecraft.getInstance().font.width(rightText), (float) (y + 6), -1);
+ }
+
+ @Override
+ public int getItemHeight() {
+ return 24;
+ }
+
+ @Override
+ public List<? extends GuiEventListener> children() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<? extends NarratableEntry> narratables() {
+ return Collections.emptyList();
+ }
+ }
+
+ private enum SortType {
+ COUNT,
+ ID
+ }
+}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/PerformanceScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/PerformanceScreen.java
index e005494c8..2f25fab3e 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/PerformanceScreen.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/PerformanceScreen.java
@@ -26,17 +26,21 @@ package me.shedaniel.rei.impl.client.gui.performance;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.datafixers.util.Pair;
import me.shedaniel.clothconfig2.gui.widget.DynamicElementListWidget;
+import me.shedaniel.math.Rectangle;
import me.shedaniel.rei.RoughlyEnoughItemsCore;
import me.shedaniel.rei.api.common.plugins.REIPlugin;
import me.shedaniel.rei.api.common.plugins.REIPluginProvider;
+import me.shedaniel.rei.api.common.util.CollectionUtils;
+import me.shedaniel.rei.impl.client.gui.modules.Menu;
+import me.shedaniel.rei.impl.client.gui.modules.entries.ToggleMenuEntry;
import me.shedaniel.rei.impl.client.gui.performance.entry.PerformanceEntryImpl;
import me.shedaniel.rei.impl.client.gui.performance.entry.SubCategoryListEntry;
+import me.shedaniel.rei.impl.client.gui.screen.ScreenWithMenu;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiComponent;
import net.minecraft.client.gui.components.Button;
-import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextColor;
import net.minecraft.util.FormattedCharSequence;
@@ -49,7 +53,7 @@ import java.util.stream.Collectors;
import static java.util.concurrent.TimeUnit.*;
@Environment(EnvType.CLIENT)
-public class PerformanceScreen extends Screen {
+public class PerformanceScreen extends ScreenWithMenu {
private Runnable onClose;
public PerformanceScreen(Runnable onClose) {
@@ -58,6 +62,7 @@ public class PerformanceScreen extends Screen {
}
private PerformanceEntryListWidget list;
+ private SortType sortType = SortType.ORDER;
/*
* Copyright (C) 2008 The Guava Authors
@@ -143,8 +148,22 @@ public class PerformanceScreen extends Screen {
this.onClose = null;
}, Supplier::get) {});
}
+ {
+ Component text = Component.translatable("text.rei.sort");
+ Rectangle bounds = new Rectangle(this.width - 4 - Minecraft.getInstance().font.width(text) - 10, 4, Minecraft.getInstance().font.width(text) + 10, 20);
+ addRenderableWidget(new Button(bounds.x, bounds.y, bounds.width, bounds.height, text, button -> {
+ this.setMenu(new Menu(bounds, CollectionUtils.map(SortType.values(), type -> {
+ return ToggleMenuEntry.of(Component.translatable("text.rei.sort.by", type.name().toLowerCase(Locale.ROOT)), () -> false, o -> {
+ this.closeMenu();
+ this.sortType = type;
+ this.init(this.minecraft, this.width, this.height);
+ });
+ }), false));
+ }, Supplier::get) {});
+ }
list = new PerformanceEntryListWidget();
long[] totalTime = {0};
+ List<SubCategoryListEntry> subCategories = new ArrayList<>();
RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.getStages().forEach((stage, inner) -> {
List<PerformanceEntryImpl> entries = new ArrayList<>();
inner.times().forEach((obj, time) -> {
@@ -159,9 +178,15 @@ public class PerformanceScreen extends Screen {
entries.add(new PerformanceEntryImpl(Component.literal("Miscellaneous Operations"), inner.totalNano() - separateTime));
}
totalTime[0] += Math.max(inner.totalNano(), separateTime);
- entries.sort(Comparator.<PerformanceEntryImpl>comparingLong(value -> value.time).reversed());
- list.addItem(new SubCategoryListEntry(Component.literal(stage), (List<PerformanceScreen.PerformanceEntry>) (List<? extends PerformanceScreen.PerformanceEntry>) entries, Math.max(inner.totalNano(), separateTime), false));
+ if (this.sortType == SortType.DURATION) {
+ entries.sort(Comparator.<PerformanceEntryImpl>comparingLong(value -> value.time).reversed());
+ }
+ subCategories.add(new SubCategoryListEntry(Component.literal(stage), (List<PerformanceScreen.PerformanceEntry>) (List<? extends PerformanceScreen.PerformanceEntry>) entries, Math.max(inner.totalNano(), separateTime), false));
});
+ if (this.sortType == SortType.DURATION) {
+ subCategories.sort(Comparator.comparingLong(SubCategoryListEntry::getTotalTime).reversed());
+ }
+ subCategories.forEach(list::addItem);
list.children().add(0, new PerformanceEntryImpl(Component.literal("Total Load Time"), totalTime[0]));
addWidget(list);
}
@@ -213,4 +238,9 @@ public class PerformanceScreen extends Screen {
return width - 6;
}
}
+
+ private enum SortType {
+ ORDER,
+ DURATION
+ }
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/SubCategoryListEntry.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/SubCategoryListEntry.java
index f6f55f92c..5f78dd6b2 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/SubCategoryListEntry.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/SubCategoryListEntry.java
@@ -157,6 +157,10 @@ public class SubCategoryListEntry extends PerformanceScreen.PerformanceEntry imp
return Collections.emptyList();
}
+ public long getTotalTime() {
+ return totalTime;
+ }
+
public class CategoryLabelWidget implements GuiEventListener {
private final Rectangle rectangle = new Rectangle();
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ScreenWithMenu.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ScreenWithMenu.java
new file mode 100644
index 000000000..ab8df5df8
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ScreenWithMenu.java
@@ -0,0 +1,89 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.client.gui.screen;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import me.shedaniel.rei.impl.client.gui.modules.Menu;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.network.chat.Component;
+import org.jetbrains.annotations.Nullable;
+
+public class ScreenWithMenu extends Screen {
+ @Nullable
+ private Menu menu;
+
+ protected ScreenWithMenu(Component component) {
+ super(component);
+ }
+
+ @Override
+ public void render(PoseStack poses, int mouseX, int mouseY, float delta) {
+ super.render(poses, mouseX, mouseY, delta);
+ if (this.menu != null) {
+ poses.pushPose();
+ poses.translate(0, 0, 400);
+ this.menu.render(poses, mouseX, mouseY, delta);
+ poses.popPose();
+ }
+ }
+
+ protected void setMenu(@Nullable Menu menu) {
+ this.menu = menu;
+ }
+
+ protected void closeMenu() {
+ this.menu = null;
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (this.menu != null) {
+ if (!this.menu.mouseClicked(mouseX, mouseY, button))
+ this.menu = null;
+ return true;
+ }
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+
+ @Override
+ public boolean mouseReleased(double mouseX, double mouseY, int button) {
+ if (this.menu != null && this.menu.mouseReleased(mouseX, mouseY, button))
+ return true;
+ return super.mouseReleased(mouseX, mouseY, button);
+ }
+
+ @Override
+ public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
+ if (this.menu != null && this.menu.mouseScrolled(mouseX, mouseY, amount))
+ return true;
+ return super.mouseScrolled(mouseX, mouseY, amount);
+ }
+
+ @Override
+ public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
+ if (this.menu != null && this.menu.mouseDragged(mouseX, mouseY, button, deltaX, deltaY))
+ return true;
+ return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
+ }
+}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java
index 58e745e34..874e94087 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java
@@ -84,6 +84,7 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.*;
+import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
@@ -271,6 +272,12 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget {
return this;
}
+ @Override
+ public Slot withEntriesListener(Consumer<Slot> listener) {
+ this.getCyclingEntries().addListener($ -> listener.accept(this));
+ return this;
+ }
+
public Slot entries(CyclingList<EntryStack<?>> stacks) {
this.getCyclingEntries().setBacking(stacks);
if (removeTagMatch) tagMatch = null;
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayCache.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayCache.java
index b65aba97c..9c725efc4 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayCache.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayCache.java
@@ -34,6 +34,10 @@ import java.util.List;
import java.util.Set;
public interface DisplayCache {
+ int cachedSize();
+
+ int notCachedSize();
+
boolean doesCache();
boolean isCached(Display display);
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayCacheImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayCacheImpl.java
index 80a73f82e..4c995c9b0 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayCacheImpl.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayCacheImpl.java
@@ -54,6 +54,16 @@ public class DisplayCacheImpl implements DisplayCache {
}
@Override
+ public int cachedSize() {
+ return this.displaysCached.size();
+ }
+
+ @Override
+ public int notCachedSize() {
+ return this.displaysNotCached.size();
+ }
+
+ @Override
public boolean doesCache() {
return this.cache;
}
@@ -66,7 +76,7 @@ public class DisplayCacheImpl implements DisplayCache {
@Override
public void add(Display display) {
if (this.cache) {
- if (!preprocessed) {
+ if (!this.preprocessed) {
this.displaysNotCached.add(display);
} else {
this.process(display);
@@ -80,7 +90,7 @@ public class DisplayCacheImpl implements DisplayCache {
@Override
public boolean remove(Display display) {
if (this.cache) {
- if (!preprocessed) {
+ if (!this.preprocessed) {
return this.displaysNotCached.remove(display);
} else {
boolean removed = this.displaysCached.remove(display);
@@ -106,7 +116,7 @@ public class DisplayCacheImpl implements DisplayCache {
@Override
public void endReload() {
if (this.cache) {
- if (preprocessed) {
+ if (this.preprocessed) {
InternalLogger.getInstance().error("DisplayCache#endReload called after preprocessed!");
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java
index d0d36130c..7ec01a9f2 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java
@@ -23,7 +23,9 @@
package me.shedaniel.rei.impl.client.registry.display;
-import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
import dev.architectury.event.EventResult;
import me.shedaniel.rei.api.client.plugins.REIClientPlugin;
import me.shedaniel.rei.api.client.registry.category.CategoryRegistry;
@@ -36,6 +38,7 @@ import me.shedaniel.rei.api.client.registry.display.visibility.DisplayVisibility
import me.shedaniel.rei.api.common.category.CategoryIdentifier;
import me.shedaniel.rei.api.common.display.Display;
import me.shedaniel.rei.api.common.plugins.PluginManager;
+import me.shedaniel.rei.api.common.registry.ReloadStage;
import me.shedaniel.rei.impl.common.InternalLogger;
import me.shedaniel.rei.impl.common.registry.RecipeManagerContextImpl;
import net.minecraft.world.item.crafting.Recipe;
@@ -55,10 +58,6 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl<REIClientPlugi
private long lastAddWarning = -1;
private DisplaysHolder displaysHolder = new DisplaysHolderImpl(false);
- public DisplayRegistryImpl() {
- super(RecipeManagerContextImpl.supplier());
- }
-
@Override
public void acceptPlugin(REIClientPlugin plugin) {
plugin.registerDisplays(this);
@@ -122,7 +121,11 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl<REIClientPlugi
@Override
public boolean isDisplayVisible(Display display) {
DisplayCategory<Display> category = (DisplayCategory<Display>) CategoryRegistry.getInstance().get(display.getCategoryIdentifier()).getCategory();
- Preconditions.checkNotNull(category, "Failed to resolve category: " + display.getCategoryIdentifier());
+ return isDisplayVisible(category, display);
+ }
+
+ public boolean isDisplayVisible(DisplayCategory<?> category, Display display) {
+ if (category == null) throw new NullPointerException("Failed to resolve category: " + display.getCategoryIdentifier());
for (DisplayVisibilityPredicate predicate : visibilityPredicates) {
try {
EventResult result = predicate.handleDisplay(category, display);
@@ -187,13 +190,8 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl<REIClientPlugi
@Override
public void endReload() {
- if (!fillers.isEmpty()) {
- List<Recipe<?>> allSortedRecipes = getAllSortedRecipes();
- for (int i = allSortedRecipes.size() - 1; i >= 0; i--) {
- Recipe<?> recipe = allSortedRecipes.get(i);
- addWithReason(recipe, DisplayAdditionReason.RECIPE_MANAGER);
- }
- }
+ InternalLogger.getInstance().debug("Found preliminary %d displays", displaySize());
+ fillSortedRecipes();
for (CategoryIdentifier<?> identifier : getAll().keySet()) {
if (CategoryRegistry.getInstance().tryGet(identifier).isEmpty()) {
@@ -201,21 +199,59 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl<REIClientPlugi
}
}
- List<Display> failedDisplays = new ArrayList<>();
+ removeFailedDisplays();
+ this.displaysHolder.endReload();
+ InternalLogger.getInstance().debug("%d displays registration have completed", displaySize());
+ }
+
+ private void fillSortedRecipes() {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ int lastSize = displaySize();
+ if (!fillers.isEmpty()) {
+ List<Recipe<?>> allSortedRecipes = getAllSortedRecipes();
+ for (int i = allSortedRecipes.size() - 1; i >= 0; i--) {
+ Recipe<?> recipe = allSortedRecipes.get(i);
+ try {
+ addWithReason(recipe, DisplayAdditionReason.RECIPE_MANAGER);
+ } catch (Throwable e) {
+ InternalLogger.getInstance().error("Failed to fill display for recipe: %s [%s]", recipe, recipe.getId(), e);
+ }
+ }
+ }
+ InternalLogger.getInstance().debug("Filled %d displays from recipe manager in %s", displaySize() - lastSize, stopwatch.stop());
+ }
+
+ private void removeFailedDisplays() {
+ Multimap<CategoryIdentifier<?>, Display> failedDisplays = Multimaps.newListMultimap(new HashMap<>(), ArrayList::new);
for (List<Display> displays : getAll().values()) {
for (Display display : displays) {
if (!DisplayValidator.validate(display)) {
- failedDisplays.add(display);
+ failedDisplays.put(display.getCategoryIdentifier(), display);
}
}
}
- for (Display display : failedDisplays) {
- this.displaysHolder.remove(display);
- }
-
- this.displaysHolder.endReload();
- InternalLogger.getInstance().debug("Registered %d displays", displaySize());
+ InternalLogger.getInstance().debug("Removing %d failed displays" + (!failedDisplays.isEmpty() ? ":" : ""), failedDisplays.size());
+ failedDisplays.asMap().entrySet().stream()
+ .sorted(Comparator.comparing(entry -> entry.getKey().toString()))
+ .forEach(entry -> {
+ InternalLogger.getInstance().debug("- %s: %d failed display" + (entry.getValue().size() == 1 ? "" : "s"), entry.getKey(), entry.getValue().size());
+ for (Display display : entry.getValue()) {
+ this.displaysHolder.remove(display);
+ }
+ });
+ }
+
+ @Override
+ public void postStage(ReloadStage stage) {
+ if (stage != ReloadStage.END) return;
+ InternalLogger.getInstance().debug("Registered displays report (%d displays, %d cached / %d not cached)" + (displaySize() > 0 ? ":" : ""),
+ displaySize(), displaysHolder().cache().cachedSize(), displaysHolder().cache().notCachedSize());
+ getAll().entrySet().stream()
+ .sorted(Comparator.comparing(entry -> entry.getKey().toString()))
+ .forEach(entry -> {
+ InternalLogger.getInstance().debug("- %s: %d display" + (entry.getValue().size() == 1 ? "" : "s"), entry.getKey(), entry.getValue().size());
+ });
}
public DisplaysHolder displaysHolder() {
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/util/OriginalRetainingCyclingList.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/OriginalRetainingCyclingList.java
index aeb557d45..50627c2a1 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/util/OriginalRetainingCyclingList.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/OriginalRetainingCyclingList.java
@@ -26,16 +26,15 @@ package me.shedaniel.rei.impl.client.util;
import com.google.common.collect.Iterables;
import org.jetbrains.annotations.Nullable;
-import java.util.AbstractList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
+import java.util.*;
+import java.util.function.Consumer;
import java.util.function.Supplier;
public class OriginalRetainingCyclingList<T> implements CyclingList.Mutable<T> {
private final Supplier<T> empty;
@Nullable
private CyclingList<T> backing = null;
+ private List<Consumer<CyclingList<T>>> listeners = List.of();
public OriginalRetainingCyclingList(Supplier<T> empty) {
this.empty = empty;
@@ -56,7 +55,9 @@ public class OriginalRetainingCyclingList<T> implements CyclingList.Mutable<T> {
@Override
public T previous() {
if (this.backing == null) return empty.get();
- return this.backing.previous();
+ T previous = this.backing.previous();
+ notifyListeners();
+ return previous;
}
@Override
@@ -72,7 +73,9 @@ public class OriginalRetainingCyclingList<T> implements CyclingList.Mutable<T> {
@Override
public T next() {
if (this.backing == null) return empty.get();
- return this.backing.next();
+ T next = this.backing.next();
+ notifyListeners();
+ return next;
}
@Override
@@ -86,11 +89,16 @@ public class OriginalRetainingCyclingList<T> implements CyclingList.Mutable<T> {
mutable.add(entry);
this.backing = mutable;
}
+
+ notifyListeners();
}
@Override
public void resetToStart() {
- if (this.backing != null) this.backing.resetToStart();
+ if (this.backing != null) {
+ this.backing.resetToStart();
+ notifyListeners();
+ };
}
@Override
@@ -118,6 +126,8 @@ public class OriginalRetainingCyclingList<T> implements CyclingList.Mutable<T> {
mutable.addAll(entries);
this.backing = mutable;
}
+
+ notifyListeners();
}
}
@@ -128,10 +138,13 @@ public class OriginalRetainingCyclingList<T> implements CyclingList.Mutable<T> {
} else {
this.backing = null;
}
+
+ notifyListeners();
}
public void setBacking(@Nullable CyclingList<T> backing) {
this.backing = backing;
+ notifyListeners();
}
private static <T> AbstractList<T> getListFromCollection(Collection<? extends T> entries) {
@@ -173,4 +186,19 @@ public class OriginalRetainingCyclingList<T> implements CyclingList.Mutable<T> {
if (this.backing == null) return CyclingList.of(this.empty);
return this.backing;
}
+
+ public void addListener(Consumer<CyclingList<T>> listener) {
+ if (this.listeners instanceof ArrayList<Consumer<CyclingList<T>>> list) {
+ list.add(listener);
+ } else {
+ this.listeners = new ArrayList<>(this.listeners);
+ this.listeners.add(listener);
+ }
+ }
+
+ private void notifyListeners() {
+ for (Consumer<CyclingList<T>> listener : this.listeners) {
+ listener.accept(this);
+ }
+ }
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java
index 2d2e6fada..0047638aa 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java
@@ -111,7 +111,7 @@ public class ViewsImpl implements Views {
forCategories(processingVisibilityHandlers, filteringCategories, displayRegistry, result, (configuration, categoryId, displays, set) -> {
if (categories.contains(categoryId)) { // If the category is in the search, add all displays
for (Display display : displays) {
- if (!processingVisibilityHandlers || displayRegistry.isDisplayVisible(display)) {
+ if (!processingVisibilityHandlers || ((DisplayRegistryImpl) displayRegistry).isDisplayVisible(configuration.getCategory(), display)) {
set.add(display);
}
}
@@ -121,7 +121,7 @@ public class ViewsImpl implements Views {
return;
}
for (Display display : displays) {
- if (processingVisibilityHandlers && !displayRegistry.isDisplayVisible(display)) continue;
+ if (processingVisibilityHandlers && !((DisplayRegistryImpl) displayRegistry).isDisplayVisible(configuration.getCategory(), display)) continue;
if (!recipesForStacks.isEmpty()) {
if (isRecipesFor(displaysHolder, recipesForStacks, display)) {
set.add(display);
@@ -171,7 +171,7 @@ public class ViewsImpl implements Views {
forCategories(processingVisibilityHandlers, filteringCategories, displayRegistry, result, (configuration, categoryId, displays, set) -> {
if (categories.contains(categoryId)) return;
for (Display display : displays) {
- if (processingVisibilityHandlers && !displayRegistry.isDisplayVisible(display)) continue;
+ if (processingVisibilityHandlers && !((DisplayRegistryImpl) displayRegistry).isDisplayVisible(configuration.getCategory(), display)) continue;
if (!recipesForStacksWildcard.isEmpty()) {
if (isRecipesFor(displaysHolder, recipesForStacksWildcard, display)) {
set.add(display);
@@ -193,7 +193,7 @@ public class ViewsImpl implements Views {
if (isStackWorkStationOfCategory(configuration, usagesFor)) {
categories.add(categoryId);
if (processingVisibilityHandlers) {
- set.addAll(CollectionUtils.filterToSet(displays, displayRegistry::isDisplayVisible));
+ set.addAll(CollectionUtils.filterToSet(displays, display -> ((DisplayRegistryImpl) displayRegistry).isDisplayVisible(configuration.getCategory(), display)));
} else {
set.addAll(displays);
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java
index d5140f2c8..b0d7abb52 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java
@@ -38,12 +38,11 @@ import me.shedaniel.rei.api.common.plugins.REIPlugin;
import me.shedaniel.rei.api.common.plugins.REIPluginProvider;
import me.shedaniel.rei.api.common.registry.ReloadStage;
import me.shedaniel.rei.api.common.registry.Reloadable;
-import me.shedaniel.rei.api.common.util.CollectionUtils;
import me.shedaniel.rei.impl.common.InternalLogger;
import me.shedaniel.rei.impl.common.logging.performance.PerformanceLogger;
import net.minecraft.client.Minecraft;
import net.minecraft.server.MinecraftServer;
-import org.apache.commons.lang3.tuple.MutablePair;
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@@ -52,7 +51,6 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.UnaryOperator;
-import java.util.stream.Stream;
@ApiStatus.Internal
public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<P>, PluginView<P> {
@@ -62,7 +60,7 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
private final UnaryOperator<PluginView<P>> view;
@Nullable
private ReloadStage reloading = null;
- private List<ReloadStage> observedStages = new ArrayList<>();
+ private final List<ReloadStage> observedStages = new ArrayList<>();
private final List<REIPluginProvider<P>> plugins = new ArrayList<>();
private final Stopwatch reloadStopwatch = Stopwatch.createUnstarted();
private boolean forcedMainThread;
@@ -127,15 +125,7 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
return FluentIterable.concat(Iterables.transform(plugins, REIPluginProvider::provide));
}
- private static class PluginWrapper<P extends REIPlugin<?>> {
- private final P plugin;
- private final REIPluginProvider<P> provider;
-
- public PluginWrapper(P plugin, REIPluginProvider<P> provider) {
- this.plugin = plugin;
- this.provider = provider;
- }
-
+ private record PluginWrapper<P extends REIPlugin<?>>(P plugin, REIPluginProvider<P> provider) {
public double getPriority() {
return plugin.getPriority();
}
@@ -143,7 +133,7 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
public String getPluginProviderName() {
String providerName = provider.getPluginProviderName();
- if (provider.provide().size() >= 1) {
+ if (!provider.provide().isEmpty()) {
String pluginName = plugin.getPluginProviderName();
if (!providerName.equals(pluginName)) {
@@ -158,32 +148,36 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
@SuppressWarnings("RedundantTypeArguments")
public FluentIterable<PluginWrapper<P>> getPluginWrapped() {
return FluentIterable.<PluginWrapper<P>>concat(Iterables.<REIPluginProvider<P>, Iterable<PluginWrapper<P>>>transform(plugins, input -> Iterables.<P, PluginWrapper<P>>transform(input.provide(),
- plugin -> new PluginWrapper(plugin, input))));
+ plugin -> new PluginWrapper<>(plugin, input))));
}
private class SectionClosable implements Closeable {
- private ReloadStage stage;
- private MutablePair<Stopwatch, String> sectionData;
+ private final PluginReloadContext context;
+ private final String section;
+ private final Stopwatch stopwatch;
- public SectionClosable(ReloadStage stage, String section) {
- this.stage = stage;
- this.sectionData = new MutablePair<>(Stopwatch.createUnstarted(), "");
- sectionData.setRight(section);
- InternalLogger.getInstance().trace("[" + name(pluginClass) + " " + stage + "] Reloading Section: \"%s\"", section);
- sectionData.getLeft().reset().start();
+ public SectionClosable(PluginReloadContext context, String section) {
+ this.context = context;
+ this.section = section;
+ this.stopwatch = Stopwatch.createStarted();
+ InternalLogger.getInstance().trace("[" + name(pluginClass) + " " + context.stage() + "] Reloading Section: \"%s\"", section);
}
@Override
public void close() {
- sectionData.getLeft().stop();
- String section = sectionData.getRight();
- InternalLogger.getInstance().trace("[" + name(pluginClass) + " " + stage + "] Reloading Section: \"%s\" done in %s", section, sectionData.getLeft().toString());
- sectionData.getLeft().reset();
+ this.stopwatch.stop();
+ InternalLogger.getInstance().trace("[" + name(pluginClass) + " " + context.stage() + "] Reloading Section: \"%s\" done in %s", this.section, this.stopwatch);
+ this.stopwatch.reset();
+ try {
+ context.interruptionContext().checkInterrupted();
+ } catch (InterruptedException exception) {
+ ExceptionUtils.rethrow(exception);
+ }
}
}
- private SectionClosable section(ReloadStage stage, String section) {
- return new SectionClosable(stage, section);
+ private SectionClosable section(PluginReloadContext context, String section) {
+ return new SectionClosable(context, section);
}
@FunctionalInterface
@@ -191,9 +185,9 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
void accept(boolean respectMainThread, Runnable task);
}
- private void pluginSection(ReloadStage stage, String sectionName, List<PluginWrapper<P>> list, @Nullable Reloadable<?> reloadable, BiConsumer<PluginWrapper<P>, SectionPluginSink> consumer) {
+ private void pluginSection(PluginReloadContext context, String sectionName, List<PluginWrapper<P>> list, @Nullable Reloadable<?> reloadable, BiConsumer<PluginWrapper<P>, SectionPluginSink> consumer) throws InterruptedException {
for (PluginWrapper<P> wrapper : list) {
- try (SectionClosable section = section(stage, sectionName + wrapper.getPluginProviderName() + "/")) {
+ try (SectionClosable section = section(context, sectionName + wrapper.getPluginProviderName() + "/")) {
consumer.accept(wrapper, (respectMainThread, runnable) -> {
if (!respectMainThread || reloadable == null || !wrapper.plugin.shouldBeForcefullyDoneOnMainThread(reloadable)) {
runnable.run();
@@ -213,6 +207,7 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
}
});
} catch (Throwable throwable) {
+ if (throwable instanceof InterruptedException) throw (InterruptedException) throwable;
InternalLogger.getInstance().error(wrapper.getPluginProviderName() + " plugin failed to " + sectionName + "!", throwable);
}
}
@@ -230,13 +225,15 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
}
@Override
- public void pre(ReloadStage stage) {
- this.reloading = stage;
+ public void pre(PluginReloadContext context0) throws InterruptedException {
+ this.reloading = context0.stage();
+ PluginReloadContext context = PluginReloadContext.of(context0.stage(), context0.interruptionContext().withJob(() -> this.reloading = null));
+
List<PluginWrapper<P>> plugins = new ArrayList<>(getPluginWrapped().toList());
plugins.sort(Comparator.comparingDouble(PluginWrapper<P>::getPriority).reversed());
Collections.reverse(plugins);
InternalLogger.getInstance().debug("========================================");
- InternalLogger.getInstance().debug(name(pluginClass) + " starting pre-reload for " + stage + ".");
+ InternalLogger.getInstance().debug(name(pluginClass) + " starting pre-reload for " + context.stage() + ".");
InternalLogger.getInstance().debug("Reloadables (%d):".formatted(reloadables.size()));
for (Reloadable<P> reloadable : reloadables) {
InternalLogger.getInstance().debug(" - " + name(reloadable.getClass()));
@@ -250,47 +247,51 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
this.forceMainThreadStopwatch.reset();
this.reloadStopwatch.reset().start();
this.observedStages.clear();
- this.observedStages.add(stage);
- try (SectionClosable preRegister = section(stage, "pre-register/");
+ this.observedStages.add(context.stage());
+ try (SectionClosable preRegister = section(context, "pre-register/");
PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Pre Registration")) {
- pluginSection(stage, "pre-register/", plugins, null, (plugin, sink) -> {
+ pluginSection(context, "pre-register/", plugins, null, (plugin, sink) -> {
try (PerformanceLogger.Plugin.Inner inner = perfLogger.plugin(new Pair<>(plugin.provider, plugin.plugin))) {
sink.accept(false, () -> {
- ((REIPlugin<P>) plugin.plugin).preStage(this, stage);
+ ((REIPlugin<P>) plugin.plugin).preStage(this, context.stage());
});
}
});
+ } catch (InterruptedException exception) {
+ throw exception;
} catch (Throwable throwable) {
- this.reloading = null;
- new RuntimeException("Failed to run pre registration").printStackTrace();
+ InternalLogger.getInstance().throwException(new RuntimeException("Failed to run pre registration in stage [" + context.stage() + "]"));
}
- try (SectionClosable preStageAll = section(stage, "pre-stage/");
- PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Pre Stage " + stage.name())) {
+ try (SectionClosable preStageAll = section(context, "pre-stage/");
+ PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Pre Stage " + context.stage().name())) {
for (Reloadable<P> reloadable : reloadables) {
Class<?> reloadableClass = reloadable.getClass();
- try (SectionClosable preStage = section(stage, "pre-stage/" + name(reloadableClass) + "/");
+ try (SectionClosable preStage = section(context, "pre-stage/" + name(reloadableClass) + "/");
PerformanceLogger.Plugin.Inner inner = perfLogger.stage(name(reloadableClass))) {
- reloadable.preStage(stage);
+ reloadable.preStage(context.stage());
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ if (throwable instanceof InterruptedException) throw (InterruptedException) throwable;
+ InternalLogger.getInstance().error("Failed to run pre registration task for reloadable [" + name(reloadableClass) + "] in stage [" + context.stage() + "]", throwable);
}
}
}
this.reloading = null;
this.reloadStopwatch.stop();
InternalLogger.getInstance().debug("========================================");
- InternalLogger.getInstance().debug(name(pluginClass) + " finished pre-reload for " + stage + " in " + reloadStopwatch + ".");
+ InternalLogger.getInstance().debug(name(pluginClass) + " finished pre-reload for " + context.stage() + " in " + reloadStopwatch + ".");
InternalLogger.getInstance().debug("========================================");
}
@Override
- public void post(ReloadStage stage) {
- this.reloading = stage;
+ public void post(PluginReloadContext context0) throws InterruptedException {
+ this.reloading = context0.stage();
+ PluginReloadContext context = PluginReloadContext.of(context0.stage(), context0.interruptionContext().withJob(() -> this.reloading = null));
+
List<PluginWrapper<P>> plugins = new ArrayList<>(getPluginWrapped().toList());
plugins.sort(Comparator.comparingDouble(PluginWrapper<P>::getPriority).reversed());
Collections.reverse(plugins);
InternalLogger.getInstance().debug("========================================");
- InternalLogger.getInstance().debug(name(pluginClass) + " starting post-reload for " + stage + ".");
+ InternalLogger.getInstance().debug(name(pluginClass) + " starting post-reload for " + context.stage() + ".");
InternalLogger.getInstance().debug("Reloadables (%d):".formatted(reloadables.size()));
for (Reloadable<P> reloadable : reloadables) {
InternalLogger.getInstance().debug(" - " + name(reloadable.getClass()));
@@ -302,28 +303,29 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
InternalLogger.getInstance().debug("========================================");
this.reloadStopwatch.start();
Stopwatch postStopwatch = Stopwatch.createStarted();
- try (SectionClosable postRegister = section(stage, "post-register/");
+ try (SectionClosable postRegister = section(context, "post-register/");
PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Post Registration")) {
- pluginSection(stage, "post-register/", plugins, null, (plugin, sink) -> {
+ pluginSection(context, "post-register/", plugins, null, (plugin, sink) -> {
try (PerformanceLogger.Plugin.Inner inner = perfLogger.plugin(new Pair<>(plugin.provider, plugin.plugin))) {
sink.accept(false, () -> {
- ((REIPlugin<P>) plugin.plugin).postStage(this, stage);
+ ((REIPlugin<P>) plugin.plugin).postStage(this, context.stage());
});
}
});
} catch (Throwable throwable) {
- this.reloading = null;
- new RuntimeException("Failed to run post registration").printStackTrace();
+ if (throwable instanceof InterruptedException) throw (InterruptedException) throwable;
+ InternalLogger.getInstance().throwException(new RuntimeException("Failed to run post registration in stage [" + context.stage() + "]"));
}
- try (SectionClosable postStageAll = section(stage, "post-stage/");
- PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Pre Stage " + stage.name())) {
+ try (SectionClosable postStageAll = section(context, "post-stage/");
+ PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Pre Stage " + context.stage().name())) {
for (Reloadable<P> reloadable : reloadables) {
Class<?> reloadableClass = reloadable.getClass();
- try (SectionClosable postStage = section(stage, "post-stage/" + name(reloadableClass) + "/");
+ try (SectionClosable postStage = section(context, "post-stage/" + name(reloadableClass) + "/");
PerformanceLogger.Plugin.Inner inner = perfLogger.stage(name(reloadableClass))) {
- reloadable.postStage(stage);
+ reloadable.postStage(context.stage());
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ if (throwable instanceof InterruptedException) throw (InterruptedException) throwable;
+ InternalLogger.getInstance().error("Failed to run post registration task for reloadable [" + name(reloadableClass) + "] in stage [" + context.stage() + "]", throwable);
}
}
}
@@ -331,7 +333,7 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
this.reloadStopwatch.stop();
postStopwatch.stop();
InternalLogger.getInstance().debug("========================================");
- InternalLogger.getInstance().info(name(pluginClass) + " finished post-reload for " + stage + " in " + postStopwatch + ", totaling " + reloadStopwatch + ".");
+ InternalLogger.getInstance().info(name(pluginClass) + " finished post-reload for " + context.stage() + " in " + postStopwatch + ", totaling " + reloadStopwatch + ".");
if (forcedMainThread) {
InternalLogger.getInstance().warn("Forcing plugins to run on main thread took " + forceMainThreadStopwatch);
}
@@ -347,10 +349,20 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
@Override
public void startReload(ReloadStage stage) {
try {
+ reload(PluginReloadContext.of(stage, ReloadInterruptionContext.ofNever()));
+ } catch (InterruptedException e) {
+ ExceptionUtils.rethrow(e);
+ }
+ }
+
+ @Override
+ public void reload(PluginReloadContext context0) throws InterruptedException {
+ try {
this.reloadStopwatch.start();
Stopwatch reloadingStopwatch = Stopwatch.createStarted();
- reloading = stage;
-
+ this.reloading = context0.stage();
+ PluginReloadContext context = PluginReloadContext.of(context0.stage(), context0.interruptionContext().withJob(() -> this.reloading = null));
+
// Sort Plugins
List<PluginWrapper<P>> plugins = new ArrayList<>(getPluginWrapped().toList());
plugins.sort(Comparator.comparingDouble(PluginWrapper<P>::getPriority).reversed());
@@ -359,7 +371,7 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
// Pre Reload
String line = new String[]{"*", "=", "#", "@", "%", "~", "O", "-", "+"}[new Random().nextInt(9)].repeat(40);
InternalLogger.getInstance().info(line);
- InternalLogger.getInstance().info(name(pluginClass) + " starting main-reload for " + stage + ".");
+ InternalLogger.getInstance().info(name(pluginClass) + " starting main-reload for " + context.stage() + ".");
InternalLogger.getInstance().debug("Reloadables (%d):".formatted(reloadables.size()));
for (Reloadable<P> reloadable : reloadables) {
InternalLogger.getInstance().debug(" - " + name(reloadable.getClass()));
@@ -370,57 +382,58 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
}
InternalLogger.getInstance().info(line);
- try (SectionClosable startReloadAll = section(stage, "start-reload/");
+ try (SectionClosable startReloadAll = section(context, "start-reload/");
PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Reload Initialization")) {
for (Reloadable<P> reloadable : reloadables) {
Class<?> reloadableClass = reloadable.getClass();
- try (SectionClosable startReload = section(stage, "start-reload/" + name(reloadableClass) + "/");
+ try (SectionClosable startReload = section(context, "start-reload/" + name(reloadableClass) + "/");
PerformanceLogger.Plugin.Inner inner = perfLogger.stage(name(reloadableClass))) {
- reloadable.startReload(stage);
+ reloadable.startReload(context.stage());
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ if (throwable instanceof InterruptedException) throw (InterruptedException) throwable;
+ InternalLogger.getInstance().error("Failed to run start-reload task for reloadable [" + name(reloadableClass) + "] in stage [" + context.stage() + "]", throwable);
}
}
}
// Reload
InternalLogger.getInstance().debug("========================================");
- InternalLogger.getInstance().debug(name(pluginClass) + " started main-reload for " + stage + ".");
+ InternalLogger.getInstance().debug(name(pluginClass) + " started main-reload for " + context.stage() + ".");
InternalLogger.getInstance().debug("========================================");
for (Reloadable<P> reloadable : getReloadables()) {
Class<?> reloadableClass = reloadable.getClass();
- try (SectionClosable reloadablePlugin = section(stage, "reloadable-plugin/" + name(reloadableClass) + "/");
+ try (SectionClosable reloadablePlugin = section(context, "reloadable-plugin/" + name(reloadableClass) + "/");
PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage(name(reloadableClass))) {
try (PerformanceLogger.Plugin.Inner inner = perfLogger.stage("reloadable-plugin/" + name(reloadableClass) + "/prompt-others-before")) {
for (Reloadable<P> listener : reloadables) {
try {
- listener.beforeReloadable(stage, reloadable);
+ listener.beforeReloadable(context.stage(), reloadable);
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ InternalLogger.getInstance().error("Failed to prompt others before reloadable [" + name(reloadableClass) + "] in stage [" + context.stage() + "]", throwable);
}
}
}
- pluginSection(stage, "reloadable-plugin/" + name(reloadableClass) + "/", plugins, reloadable, (plugin, sink) -> {
+ pluginSection(context, "reloadable-plugin/" + name(reloadableClass) + "/", plugins, reloadable, (plugin, sink) -> {
try (PerformanceLogger.Plugin.Inner inner = perfLogger.plugin(new Pair<>(plugin.provider, plugin.plugin))) {
sink.accept(true, () -> {
for (Reloadable<P> listener : reloadables) {
try {
- listener.beforeReloadablePlugin(stage, reloadable, plugin.plugin);
+ listener.beforeReloadablePlugin(context.stage(), reloadable, plugin.plugin);
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ InternalLogger.getInstance().error("Failed to run pre-reloadable task for " + plugin.getPluginProviderName() + " before reloadable [" + name(reloadableClass) + "] in stage [" + context.stage() + "]", throwable);
}
}
try {
- reloadable.acceptPlugin(plugin.plugin, stage);
+ reloadable.acceptPlugin(plugin.plugin, context.stage());
} finally {
for (Reloadable<P> listener : reloadables) {
try {
- listener.afterReloadablePlugin(stage, reloadable, plugin.plugin);
+ listener.afterReloadablePlugin(context.stage(), reloadable, plugin.plugin);
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ InternalLogger.getInstance().error("Failed to run post-reloadable task for " + plugin.getPluginProviderName() + " after reloadable [" + name(reloadableClass) + "] in stage [" + context.stage() + "]", throwable);
}
}
}
@@ -431,9 +444,9 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
try (PerformanceLogger.Plugin.Inner inner = perfLogger.stage("reloadable-plugin/" + name(reloadableClass) + "/prompt-others-after")) {
for (Reloadable<P> listener : reloadables) {
try {
- listener.afterReloadable(stage, reloadable);
+ listener.afterReloadable(context.stage(), reloadable);
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ InternalLogger.getInstance().error("Failed to prompt others after reloadable [" + name(reloadableClass) + "] in stage [" + context.stage() + "]", throwable);
}
}
}
@@ -442,28 +455,30 @@ public class PluginManagerImpl<P extends REIPlugin<?>> implements PluginManager<
// Post Reload
InternalLogger.getInstance().debug("========================================");
- InternalLogger.getInstance().debug(name(pluginClass) + " ending main-reload for " + stage + ".");
+ InternalLogger.getInstance().debug(name(pluginClass) + " ending main-reload for " + context.stage() + ".");
InternalLogger.getInstance().debug("========================================");
- try (SectionClosable endReloadAll = section(stage, "end-reload/");
+ try (SectionClosable endReloadAll = section(context, "end-reload/");
PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Reload Finalization")) {
for (Reloadable<P> reloadable : reloadables) {
Class<?> reloadableClass = reloadable.getClass();
- try (SectionClosable endReload = section(stage, "end-reload/" + name(reloadableClass) + "/");
+ try (SectionClosable endReload = section(context, "end-reload/" + name(reloadableClass) + "/");
PerformanceLogger.Plugin.Inner inner = perfLogger.stage(name(reloadableClass))) {
- reloadable.endReload(stage);
+ reloadable.endReload(context.stage());
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ if (throwable instanceof InterruptedException) throw (InterruptedException) throwable;
+ InternalLogger.getInstance().error("Failed to run end-reload task for reloadable [" + name(reloadableClass) + "] in stage [" + context.stage() + "]", throwable);
}
}
}
this.reloadStopwatch.stop();
InternalLogger.getInstance().debug("========================================");
- InternalLogger.getInstance().debug(name(pluginClass) + " ended main-reload for " + stage + " in " + reloadingStopwatch.stop() + ".");
+ InternalLogger.getInstance().debug(name(pluginClass) + " ended main-reload for " + context.stage() + " in " + reloadingStopwatch.stop() + ".");
InternalLogger.getInstance().debug("========================================");
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ if (throwable instanceof InterruptedException) throw (InterruptedException) throwable;
+ InternalLogger.getInstance().error("Failed to run reload task in stage [" + context0.stage() + "]", throwable);
} finally {
reloading = null;
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/ReloadManagerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/ReloadManagerImpl.java
new file mode 100644
index 000000000..a9590131f
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/ReloadManagerImpl.java
@@ -0,0 +1,211 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.common.plugins;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Suppliers;
+import dev.architectury.platform.Platform;
+import dev.architectury.utils.Env;
+import me.shedaniel.rei.RoughlyEnoughItemsCore;
+import me.shedaniel.rei.api.client.config.ConfigObject;
+import me.shedaniel.rei.api.common.plugins.PluginManager;
+import me.shedaniel.rei.api.common.plugins.REIPlugin;
+import me.shedaniel.rei.api.common.registry.ReloadStage;
+import me.shedaniel.rei.api.common.util.CollectionUtils;
+import me.shedaniel.rei.impl.common.InternalLogger;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.function.Supplier;
+
+@ApiStatus.Internal
+public class ReloadManagerImpl {
+ private static final Supplier<Executor> RELOAD_PLUGINS = Suppliers.memoize(() -> Executors.newSingleThreadScheduledExecutor(task -> {
+ Thread thread = new Thread(task, "REI-ReloadPlugins");
+ thread.setDaemon(true);
+ thread.setUncaughtExceptionHandler(($, exception) -> {
+ if (exception instanceof InterruptedException) {
+ InternalLogger.getInstance().debug("Interrupted while reloading plugins, could be caused by a new request to reload plugins!", new UncaughtException(exception));
+ return;
+ }
+
+ InternalLogger.getInstance().throwException(new UncaughtException(exception));
+ });
+ return thread;
+ }));
+
+ private static final List<Task> RELOAD_TASKS = new CopyOnWriteArrayList<>();
+
+ private static class Task {
+ private final Future<?> future;
+ private boolean interrupted = false;
+ private boolean completed = false;
+
+ public Task(Future<?> future) {
+ this.future = future;
+ }
+ }
+
+ private static Executor executor() {
+ if (usesREIThread()) {
+ return RELOAD_PLUGINS.get();
+ } else {
+ return runnable -> {
+ try {
+ runnable.run();
+ } catch (Throwable throwable) {
+ InternalLogger.getInstance().throwException(throwable);
+ }
+ };
+ }
+ }
+
+ private static boolean usesREIThread() {
+ if (Platform.getEnvironment() == Env.CLIENT) {
+ return usesREIThreadClient();
+ } else {
+ return false;
+ }
+ }
+
+ @Environment(EnvType.CLIENT)
+ private static boolean usesREIThreadClient() {
+ return ConfigObject.getInstance().doesRegisterRecipesInAnotherThread();
+ }
+
+ public static int countRunningReloadTasks() {
+ return CollectionUtils.sumInt(RELOAD_TASKS, task -> !task.future.isDone() || !task.completed ? 1 : 0);
+ }
+
+ public static int countUninterruptedRunningReloadTasks() {
+ return CollectionUtils.sumInt(RELOAD_TASKS, task -> !task.interrupted && (!task.future.isDone() || !task.completed) ? 1 : 0);
+ }
+
+ public static void reloadPlugins(@Nullable ReloadStage start, ReloadInterruptionContext interruptionContext) {
+ InternalLogger.getInstance().debug("Starting Reload Plugins of stage " + start, new Throwable());
+ if (usesREIThread()) {
+ if ((start == ReloadStage.START || start == null) && countRunningReloadTasks() > 0) {
+ InternalLogger.getInstance().warn("Trying to start reload plugins of stage %s but found %d existing reload task(s)!", start, countRunningReloadTasks());
+ terminateReloadTasks();
+ }
+
+ if (!RELOAD_TASKS.isEmpty()) {
+ InternalLogger.getInstance().warn("Found %d existing reload task(s) after trying to terminate them!", RELOAD_TASKS.size());
+ }
+
+ Task[] task = new Task[1];
+ Future<?> future = CompletableFuture.runAsync(() -> reloadPlugins0(start, () -> interruptionContext.isInterrupted() || (task[0] != null && task[0].interrupted)), executor())
+ .whenComplete((unused, throwable) -> {
+ // Remove the future from the list of futures
+ if (task[0] != null) {
+ task[0].completed = true;
+ RELOAD_TASKS.remove(task[0]);
+ task[0] = null;
+ }
+ });
+ task[0] = new Task(future);
+ RELOAD_TASKS.add(task[0]);
+ } else {
+ reloadPlugins0(start, interruptionContext);
+ }
+ }
+
+ private static void reloadPlugins0(@Nullable ReloadStage stage, ReloadInterruptionContext interruptionContext) {
+ if (stage == null) {
+ for (ReloadStage reloadStage : ReloadStage.values()) {
+ reloadPlugins0(reloadStage, interruptionContext);
+ }
+ } else {
+ reloadPlugins0(PluginReloadContext.of(stage, interruptionContext));
+ }
+ }
+
+ private static void reloadPlugins0(PluginReloadContext context) {
+ if (context.stage() == ReloadStage.START) RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.clear();
+ try {
+ for (PluginManager<? extends REIPlugin<?>> instance : PluginManager.getActiveInstances()) {
+ instance.view().pre(context);
+ }
+ for (PluginManager<? extends REIPlugin<?>> instance : PluginManager.getActiveInstances()) {
+ instance.view().reload(context);
+ }
+ for (PluginManager<? extends REIPlugin<?>> instance : PluginManager.getActiveInstances()) {
+ instance.view().post(context);
+ }
+ } catch (InterruptedException e) {
+ InternalLogger.getInstance().debug("Interrupted while reloading plugins, could be caused by a new request to reload plugins!", e);
+ } catch (Throwable throwable) {
+ InternalLogger.getInstance().throwException(throwable);
+ }
+ }
+
+ public static void terminateReloadTasks() {
+ if (countUninterruptedRunningReloadTasks() == 0) {
+ InternalLogger.getInstance().debug("Did not fulfill the request of termination of REI reload tasks because there are no uninterrupted running tasks. This is not an error.");
+ RELOAD_TASKS.clear();
+ return;
+ }
+
+ InternalLogger.getInstance().debug("Requested the termination of REI reload tasks.");
+
+ for (Task task : RELOAD_TASKS) {
+ task.interrupted = true;
+ }
+
+ long startTerminateTime = System.currentTimeMillis();
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ while (countRunningReloadTasks() > 0) {
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ InternalLogger.getInstance().error("Thread interrupted while waiting for reload tasks to terminate!", e);
+ }
+
+ if (System.currentTimeMillis() - startTerminateTime > 5000) {
+ InternalLogger.getInstance().error("Took too long to terminate reload tasks (over 5 seconds)! Now forcefully terminating them!");
+ for (Task task : RELOAD_TASKS) {
+ task.future.cancel(Platform.isFabric());
+ }
+ break;
+ }
+ }
+
+ if (countRunningReloadTasks() == 0) {
+ RELOAD_TASKS.clear();
+ InternalLogger.getInstance().debug("Successfully terminated reload tasks in %s", stopwatch.stop());
+ } else {
+ InternalLogger.getInstance().error("Failed to terminate reload tasks! Found %d running tasks!", countRunningReloadTasks());
+ }
+ }
+
+ private static class UncaughtException extends Exception {
+ public UncaughtException(Throwable cause) {
+ super(cause);
+ }
+ }
+}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/registry/RecipeManagerContextImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/registry/RecipeManagerContextImpl.java
index da48a31ae..e1979f11a 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/common/registry/RecipeManagerContextImpl.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/registry/RecipeManagerContextImpl.java
@@ -23,34 +23,21 @@
package me.shedaniel.rei.impl.common.registry;
-import dev.architectury.utils.EnvExecutor;
-import dev.architectury.utils.GameInstance;
import me.shedaniel.rei.api.common.plugins.REIPlugin;
import me.shedaniel.rei.api.common.registry.RecipeManagerContext;
-import net.minecraft.client.Minecraft;
+import me.shedaniel.rei.impl.common.util.InstanceHelper;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeManager;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
public class RecipeManagerContextImpl<P extends REIPlugin<?>> implements RecipeManagerContext<P> {
private static final Comparator<Recipe<?>> RECIPE_COMPARATOR = Comparator.comparing((Recipe<?> o) -> o.getId().getNamespace()).thenComparing(o -> o.getId().getPath());
- private final Supplier<RecipeManager> recipeManager;
private List<Recipe<?>> sortedRecipes = null;
- public RecipeManagerContextImpl(Supplier<RecipeManager> recipeManager) {
- this.recipeManager = recipeManager;
- }
-
- public static Supplier<RecipeManager> supplier() {
- return () -> EnvExecutor.getEnvSpecific(() -> () -> Minecraft.getInstance().getConnection().getRecipeManager(),
- () -> () -> GameInstance.getServer().getRecipeManager());
- }
-
@Override
public List<Recipe<?>> getAllSortedRecipes() {
if (sortedRecipes == null) {
@@ -62,7 +49,7 @@ public class RecipeManagerContextImpl<P extends REIPlugin<?>> implements RecipeM
@Override
public RecipeManager getRecipeManager() {
- return recipeManager.get();
+ return InstanceHelper.getInstance().recipeManager();
}
@Override
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/util/InstanceHelper.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/util/InstanceHelper.java
new file mode 100644
index 000000000..e83d72467
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/util/InstanceHelper.java
@@ -0,0 +1,125 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.common.util;
+
+import dev.architectury.platform.Platform;
+import dev.architectury.utils.Env;
+import dev.architectury.utils.GameInstance;
+import me.shedaniel.rei.impl.common.InternalLogger;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.multiplayer.ClientPacketListener;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.world.item.crafting.RecipeManager;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.ref.WeakReference;
+
+@ApiStatus.Internal
+public final class InstanceHelper {
+ private static final InstanceHelper INSTANCE = new InstanceHelper();
+ private WeakReference<RegistryAccess> registryAccessRef;
+ private WeakReference<RecipeManager> recipeManagerRef;
+ private boolean warnedRegistryAccess;
+ private boolean warnedRecipeManager;
+
+ public static InstanceHelper getInstance() {
+ return INSTANCE;
+ }
+
+ public RegistryAccess registryAccess() {
+ RegistryAccess access = this.registryAccessRef == null ? null : this.registryAccessRef.get();
+ if (access != null) {
+ return access;
+ }
+
+ if (Platform.getEnvironment() == Env.CLIENT) {
+ access = registryAccessFromClient();
+ } else if (GameInstance.getServer() != null) {
+ access = GameInstance.getServer().registryAccess();
+ }
+
+ if (access == null && !this.warnedRegistryAccess) {
+ this.warnedRegistryAccess = true;
+
+ InternalLogger.getInstance().throwException(new IllegalStateException("Cannot get registry access!"));
+ return RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY);
+ }
+
+ return access;
+ }
+
+ public RecipeManager recipeManager() {
+ RecipeManager manager = this.recipeManagerRef == null ? null : this.recipeManagerRef.get();
+ if (manager != null) {
+ return manager;
+ }
+
+ if (Platform.getEnvironment() == Env.CLIENT) {
+ manager = recipeManagerFromClient();
+ } else if (GameInstance.getServer() != null) {
+ manager = GameInstance.getServer().getRecipeManager();
+ }
+
+ if (manager == null && !this.warnedRegistryAccess) {
+ this.warnedRegistryAccess = true;
+
+ throw new IllegalStateException("Cannot get recipe manager!");
+ }
+
+ return manager;
+ }
+
+ @Environment(EnvType.CLIENT)
+ @Nullable
+ public static ClientPacketListener connectionFromClient() {
+ if (Minecraft.getInstance().level != null) {
+ return Minecraft.getInstance().level.connection;
+ } else if (Minecraft.getInstance().getConnection() != null) {
+ return Minecraft.getInstance().getConnection();
+ } else if (Minecraft.getInstance().gameMode != null) {
+ // Sometimes the packet is sent way too fast and is between the connection and the level, better safe than sorry
+ return Minecraft.getInstance().gameMode.connection;
+ }
+
+ return null;
+ }
+
+ @Environment(EnvType.CLIENT)
+ private static RegistryAccess registryAccessFromClient() {
+ ClientPacketListener connection = connectionFromClient();
+ if (connection == null) return null;
+ return connection.registryAccess();
+ }
+
+ @Environment(EnvType.CLIENT)
+ private static RecipeManager recipeManagerFromClient() {
+ ClientPacketListener connection = connectionFromClient();
+ if (connection == null) return null;
+ return connection.getRecipeManager();
+ }
+}
diff --git a/runtime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json b/runtime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json
index 0146868cf..5568968b6 100755
--- a/runtime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json
+++ b/runtime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json
@@ -79,6 +79,10 @@
"text.rei.tag_accept": "Tag: #%s",
"text.rei.missing": "Missing following:",
"text.rei.performance": "Performance Analysis",
+ "text.rei.display_registry_analysis": "Display Registry Analysis",
+ "text.rei.display_registry_analysis.displays": "%d display(s)",
+ "text.rei.sort": "Sort",
+ "text.rei.sort.by": "Sort by %s",
"text.rei.addons": "REI Addons",
"text.rei.shapeless": "Shapeless",
"text.rei.crafting.firework.gunpowder.amount": "The amount of gunpowder affects the flight duration of the firework.",
@@ -451,6 +455,8 @@
"config.rei.options.debug.search_performance.desc": "Verbose console print out for analysing search performance.",
"config.rei.options.debug.entry_list_performance": "Entry List Performance",
"config.rei.options.debug.entry_list_performance.desc": "Display the time used to render entries on the entry list.",
+ "config.rei.options.debug.display_registry_analysis": "Display Registry Analysis",
+ "config.rei.options.debug.display_registry_analysis.desc": "Break-down in registration of displays in each category.",
"config.rei.options.groups.reset.reload": "Reload",
"config.rei.options.reset.reload_plugins": "Reload Plugins",
"config.rei.options.reset.reload_plugins.desc": "Resets the current loaded data and reload all data and plugins.",