aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorshedaniel <daniel@shedaniel.me>2024-09-04 16:20:39 +0900
committershedaniel <daniel@shedaniel.me>2024-09-06 18:20:41 +0900
commita31f7254d76add6c34f134713c131463602f8cef (patch)
tree0631d373694572a8a745d0e6241585a71b8640be
parent3cf87de5b6fea02eb26b747761f8f85ad0b65f51 (diff)
downloadRoughlyEnoughItems-a31f7254d76add6c34f134713c131463602f8cef.tar.gz
RoughlyEnoughItems-a31f7254d76add6c34f134713c131463602f8cef.tar.bz2
RoughlyEnoughItems-a31f7254d76add6c34f134713c131463602f8cef.zip
New plugin reload setup
1. Plugin reloads will now interrupt existing reloading tasks if a new plugin reload has been requested 2. Plugin reloads will be automatically interrupted when the player leaves the level / the level is removed 3. More logging in DisplayRegistryImpl showing the stats of displays 4. Failure in filling recipes will now not stop the caching of display lookup 5. Slightly improve performance of checking display visibility on display lookup
-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/AllREIConfigOptions.java2
-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/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
16 files changed, 665 insertions, 205 deletions
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 48fa4cc1b..db77d73d0 100644
--- a/fabric/src/main/resources/roughlyenoughitems.accessWidener
+++ b/fabric/src/main/resources/roughlyenoughitems.accessWidener
@@ -37,4 +37,6 @@ accessible method net/minecraft/client/gui/screens/Screen
accessible method net/minecraft/client/gui/screens/Screen renderTooltipInternal (Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;II)V
accessible method net/minecraft/client/renderer/RenderType create (Ljava/lang/String;Lcom/mojang/blaze3d/vertex/VertexFormat;Lcom/mojang/blaze3d/vertex/VertexFormat$Mode;ILnet/minecraft/client/renderer/RenderType$CompositeState;)Lnet/minecraft/client/renderer/RenderType$CompositeRenderType;
accessible field net/minecraft/tags/TagEntry tag Z
-accessible field net/minecraft/tags/TagEntry id Lnet/minecraft/resources/ResourceLocation; \ No newline at end of file
+accessible field net/minecraft/tags/TagEntry id Lnet/minecraft/resources/ResourceLocation;
+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 5d520302f..4c71d137c 100644
--- a/forge/src/main/resources/META-INF/accesstransformer.cfg
+++ b/forge/src/main/resources/META-INF/accesstransformer.cfg
@@ -37,4 +37,6 @@ public net.minecraft.client.gui.screens.Screen tooltipStack
public net.minecraft.client.renderer.RenderType m_173209_(Ljava/lang/String;Lcom/mojang/blaze3d/vertex/VertexFormat;Lcom/mojang/blaze3d/vertex/VertexFormat$Mode;ILnet/minecraft/client/renderer/RenderType$CompositeState;)Lnet/minecraft/client/renderer/RenderType$CompositeRenderType;
public net.minecraft.client.renderer.RenderType$OutlineProperty
public net.minecraft.client.renderer.RenderType$CompositeState
-public net.minecraft.tags.TagEntry f_215914_ # tag \ No newline at end of file
+public net.minecraft.tags.TagEntry f_215914_ # tag
+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 96a697253..271d785e5 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/AllREIConfigOptions.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/config/options/AllREIConfigOptions.java
index 30340bfba..b9596c200 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.*;
@@ -248,7 +247,6 @@ public interface AllREIConfigOptions {
.enabledDisabled();
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/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/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..a08d9f496
--- /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.Registry;
+import net.minecraft.core.RegistryAccess;
+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(Registry.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();
+ }
+}