From a31f7254d76add6c34f134713c131463602f8cef Mon Sep 17 00:00:00 2001 From: shedaniel Date: Wed, 4 Sep 2024 16:20:39 +0900 Subject: [PATCH 1/7] 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 --- .../rei/api/common/plugins/PluginView.java | 21 +- .../common/plugins/PluginReloadContext.java | 48 ++++ .../plugins/ReloadInterruptionContext.java | 63 ++++++ .../roughlyenoughitems.accessWidener | 4 +- .../resources/META-INF/accesstransformer.cfg | 4 +- .../shedaniel/rei/RoughlyEnoughItemsCore.java | 32 +-- .../rei/RoughlyEnoughItemsCoreClient.java | 50 ++--- .../config/options/AllREIConfigOptions.java | 2 - .../client/registry/display/DisplayCache.java | 4 + .../registry/display/DisplayCacheImpl.java | 16 +- .../registry/display/DisplayRegistryImpl.java | 78 +++++-- .../rei/impl/client/view/ViewsImpl.java | 8 +- .../common/plugins/PluginManagerImpl.java | 187 +++++++++------- .../common/plugins/ReloadManagerImpl.java | 211 ++++++++++++++++++ .../registry/RecipeManagerContextImpl.java | 17 +- .../rei/impl/common/util/InstanceHelper.java | 125 +++++++++++ 16 files changed, 665 insertions(+), 205 deletions(-) create mode 100644 api/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginReloadContext.java create mode 100644 api/src/main/java/me/shedaniel/rei/impl/common/plugins/ReloadInterruptionContext.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/ReloadManagerImpl.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/util/InstanceHelper.java 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 void registerPlugin(REIPluginProvider plugin) { } @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.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.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 static void attachCommonInternals() { UnaryOperator.identity(), new EntryTypeRegistryImpl(), new EntrySettingsAdapterRegistryImpl(), - new RecipeManagerContextImpl<>(RecipeManagerContextImpl.supplier()), + new RecipeManagerContextImpl<>(), new ItemComparatorRegistryImpl(), new FluidComparatorRegistryImpl(), new DisplaySerializerRegistryImpl(), @@ -147,28 +146,6 @@ public static void attachCommonInternals() { new SlotAccessorRegistryImpl()), "serverPluginManager"); } - public static void _reloadPlugins(@Nullable ReloadStage stage) { - if (stage == null) { - for (ReloadStage reloadStage : ReloadStage.values()) { - _reloadPlugins(reloadStage); - } - return; - } - try { - for (PluginManager> instance : PluginManager.getActiveInstances()) { - instance.view().pre(stage); - } - for (PluginManager> instance : PluginManager.getActiveInstances()) { - instance.startReload(stage); - } - for (PluginManager> 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 void onInitialize() { 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.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.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 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 PRE_UPDATE_RECIPES = EventFactory.createLoop(); public static final Event 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> RELOAD_TASKS = new CopyOnWriteArrayList<>(); public static void attachClientInternals() { InternalWidgets.attach(); @@ -317,24 +307,25 @@ private static boolean _shouldReturn(Screen screen) { 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 stages = ((PluginManagerImpl>) 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 static boolean resetFocused(Screen screen) { 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 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 @@ static CompositeOption make(String id, Function bind .enabledDisabled(); CompositeOption 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.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 @@ -53,6 +53,16 @@ public DisplayCacheImpl(boolean init) { this.displaysByOutput = createSetMultimap(); } + @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 boolean isCached(Display display) { @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 void add(Display display) { @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 boolean remove(Display display) { @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.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 category = (DisplayCategory) 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 void startReload() { @Override public void endReload() { - if (!fillers.isEmpty()) { - List> 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 void endReload() { } } - List 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> 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, Display> failedDisplays = Multimaps.newListMultimap(new HashMap<>(), ArrayList::new); for (List 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 @@ private static Map, List> _buildMapFor(ViewSearc 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 @@ private static Map, List> _buildMapFor(ViewSearc 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 @@ private static Map, List> _buildMapFor(ViewSearc 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 @@ private static Map, List> _buildMapFor(ViewSearc 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.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.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.UnaryOperator; -import java.util.stream.Stream; @ApiStatus.Internal public class PluginManagerImpl

> implements PluginManager

, PluginView

{ @@ -62,7 +60,7 @@ public class PluginManagerImpl

> implements PluginManager< private final UnaryOperator> view; @Nullable private ReloadStage reloading = null; - private List observedStages = new ArrayList<>(); + private final List observedStages = new ArrayList<>(); private final List> plugins = new ArrayList<>(); private final Stopwatch reloadStopwatch = Stopwatch.createUnstarted(); private boolean forcedMainThread; @@ -127,15 +125,7 @@ public FluentIterable

getPlugins() { return FluentIterable.concat(Iterables.transform(plugins, REIPluginProvider::provide)); } - private static class PluginWrapper

> { - private final P plugin; - private final REIPluginProvider

provider; - - public PluginWrapper(P plugin, REIPluginProvider

provider) { - this.plugin = plugin; - this.provider = provider; - } - + private record PluginWrapper

>(P plugin, REIPluginProvider

provider) { public double getPriority() { return plugin.getPriority(); } @@ -143,7 +133,7 @@ public double getPriority() { 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 String getPluginProviderName() { @SuppressWarnings("RedundantTypeArguments") public FluentIterable> getPluginWrapped() { return FluentIterable.>concat(Iterables., Iterable>>transform(plugins, input -> Iterables.>transform(input.provide(), - plugin -> new PluginWrapper(plugin, input)))); + plugin -> new PluginWrapper<>(plugin, input)))); } private class SectionClosable implements Closeable { - private ReloadStage stage; - private MutablePair 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 @@ private interface SectionPluginSink { void accept(boolean respectMainThread, Runnable task); } - private void pluginSection(ReloadStage stage, String sectionName, List> list, @Nullable Reloadable reloadable, BiConsumer, SectionPluginSink> consumer) { + private void pluginSection(PluginReloadContext context, String sectionName, List> list, @Nullable Reloadable reloadable, BiConsumer, SectionPluginSink> consumer) throws InterruptedException { for (PluginWrapper

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 @@ private void pluginSection(ReloadStage stage, String sectionName, List this.reloading = null)); + List> plugins = new ArrayList<>(getPluginWrapped().toList()); plugins.sort(Comparator.comparingDouble(PluginWrapper

::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

reloadable : reloadables) { InternalLogger.getInstance().debug(" - " + name(reloadable.getClass())); @@ -250,47 +247,51 @@ public void pre(ReloadStage stage) { 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

) plugin.plugin).preStage(this, stage); + ((REIPlugin

) 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

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> plugins = new ArrayList<>(getPluginWrapped().toList()); plugins.sort(Comparator.comparingDouble(PluginWrapper

::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

reloadable : reloadables) { InternalLogger.getInstance().debug(" - " + name(reloadable.getClass())); @@ -302,28 +303,29 @@ public void post(ReloadStage stage) { 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

) plugin.plugin).postStage(this, stage); + ((REIPlugin

) 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

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 void post(ReloadStage stage) { 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); } @@ -346,11 +348,21 @@ private static String name(Class clazz) { @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> plugins = new ArrayList<>(getPluginWrapped().toList()); plugins.sort(Comparator.comparingDouble(PluginWrapper

::getPriority).reversed()); @@ -359,7 +371,7 @@ public void startReload(ReloadStage stage) { // 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

reloadable : reloadables) { InternalLogger.getInstance().debug(" - " + name(reloadable.getClass())); @@ -370,57 +382,58 @@ public void startReload(ReloadStage stage) { } 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

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

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

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

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

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 void startReload(ReloadStage stage) { try (PerformanceLogger.Plugin.Inner inner = perfLogger.stage("reloadable-plugin/" + name(reloadableClass) + "/prompt-others-after")) { for (Reloadable

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 void startReload(ReloadStage stage) { // 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

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 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 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> instance : PluginManager.getActiveInstances()) { + instance.view().pre(context); + } + for (PluginManager> instance : PluginManager.getActiveInstances()) { + instance.view().reload(context); + } + for (PluginManager> 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

> implements RecipeManagerContext

{ private static final Comparator> RECIPE_COMPARATOR = Comparator.comparing((Recipe o) -> o.getId().getNamespace()).thenComparing(o -> o.getId().getPath()); - private final Supplier recipeManager; private List> sortedRecipes = null; - public RecipeManagerContextImpl(Supplier recipeManager) { - this.recipeManager = recipeManager; - } - - public static Supplier supplier() { - return () -> EnvExecutor.getEnvSpecific(() -> () -> Minecraft.getInstance().getConnection().getRecipeManager(), - () -> () -> GameInstance.getServer().getRecipeManager()); - } - @Override public List> getAllSortedRecipes() { if (sortedRecipes == null) { @@ -62,7 +49,7 @@ public List> getAllSortedRecipes() { @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 registryAccessRef; + private WeakReference 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(); + } +} From 428d5359f0aff3b9922c7a80058f5214cf7f8340 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Fri, 6 Sep 2024 20:49:27 +0900 Subject: [PATCH 2/7] New Display Registry Analysis in REI config, with options to sort the entries --- .../config/options/AllREIConfigGroups.java | 3 +- .../config/options/AllREIConfigOptions.java | 4 + .../DisplayRegistryInfoScreen.java | 173 ++++++++++++++++++ .../gui/performance/PerformanceScreen.java | 38 +++- .../entry/SubCategoryListEntry.java | 4 + .../client/gui/screen/ScreenWithMenu.java | 89 +++++++++ .../assets/roughlyenoughitems/lang/en_us.json | 6 + 7 files changed, 312 insertions(+), 5 deletions(-) create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/DisplayRegistryInfoScreen.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/ScreenWithMenu.java 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 @@ static OptionGroup make(String id) { 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 b9596c200..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 @@ -36,6 +36,7 @@ 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; @@ -245,6 +246,9 @@ static CompositeOption make(String id, Function bind .enabledDisabled(); CompositeOption ENTRY_LIST_PERFORMANCE = make("debug.entry_list_performance", i -> i.advanced.layout.debugRenderTimeRequired, (i, v) -> i.advanced.layout.debugRenderTimeRequired = v) .enabledDisabled(); + CompositeOption 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 RELOAD_PLUGINS = make("reset.reload_plugins", i -> null, (i, v) -> new Object()) .reload((access, option, onClose) -> { RoughlyEnoughItemsCoreClient.reloadPlugins(null, null); 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..289a1c4fd --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/DisplayRegistryInfoScreen.java @@ -0,0 +1,173 @@ +/* + * 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.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; + })); + } + { + 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)); + })); + } + 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, List>> sort(Stream, List>> stream) { + return switch (sortType) { + case COUNT -> stream.sorted(Comparator., List>>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(0); + 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 { + } + + private class ListWidget extends DynamicElementListWidget { + 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 children() { + return Collections.emptyList(); + } + + @Override + public List 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 239489a71..29d170779 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 @@ 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; @@ -48,7 +52,7 @@ 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) { @@ -57,6 +61,7 @@ public PerformanceScreen(Runnable onClose) { } private PerformanceEntryListWidget list; + private SortType sortType = SortType.ORDER; /* * Copyright (C) 2008 The Guava Authors @@ -142,8 +147,22 @@ public void init() { this.onClose = null; })); } + { + 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)); + })); + } list = new PerformanceEntryListWidget(); long[] totalTime = {0}; + List subCategories = new ArrayList<>(); RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.getStages().forEach((stage, inner) -> { List entries = new ArrayList<>(); inner.times().forEach((obj, time) -> { @@ -158,9 +177,15 @@ public void init() { entries.add(new PerformanceEntryImpl(Component.literal("Miscellaneous Operations"), inner.totalNano() - separateTime)); } totalTime[0] += Math.max(inner.totalNano(), separateTime); - entries.sort(Comparator.comparingLong(value -> value.time).reversed()); - list.addItem(new SubCategoryListEntry(Component.literal(stage), (List) (List) entries, Math.max(inner.totalNano(), separateTime), false)); + if (this.sortType == SortType.DURATION) { + entries.sort(Comparator.comparingLong(value -> value.time).reversed()); + } + subCategories.add(new SubCategoryListEntry(Component.literal(stage), (List) (List) 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); } @@ -212,4 +237,9 @@ protected int getScrollbarPosition() { 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 368bd5a3a..9596fc51d 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 List narratables() { 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/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.", From b8d0975527d86b79a1f9c160415baf8a6e6cf5cb Mon Sep 17 00:00:00 2001 From: shedaniel Date: Tue, 17 Sep 2024 13:46:46 +0800 Subject: [PATCH 3/7] New Slot.withEntriesListener --- .../rei/api/client/gui/widgets/Slot.java | 6 +++ .../impl/client/gui/widget/EntryWidget.java | 7 ++++ .../util/OriginalRetainingCyclingList.java | 42 +++++++++++++++---- 3 files changed, 48 insertions(+), 7 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 @@ 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 final Slot disableBackground() { public abstract Slot entries(Collection> stacks); + @ApiStatus.Experimental + @ApiStatus.Internal + public abstract Slot withEntriesListener(Consumer listener); + public abstract EntryStack getCurrentEntry(); public abstract List> getEntries(); 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.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 EntryWidget entries(Collection> stacks) { return this; } + @Override + public Slot withEntriesListener(Consumer listener) { + this.getCyclingEntries().addListener($ -> listener.accept(this)); + return this; + } + public Slot entries(CyclingList> stacks) { this.getCyclingEntries().setBacking(stacks); if (removeTagMatch) tagMatch = null; 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 @@ 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 implements CyclingList.Mutable { private final Supplier empty; @Nullable private CyclingList backing = null; + private List>> listeners = List.of(); public OriginalRetainingCyclingList(Supplier empty) { this.empty = empty; @@ -56,7 +55,9 @@ public T peek() { @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 int previousIndex() { @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 void add(T entry) { 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 void addAll(Collection entries) { mutable.addAll(entries); this.backing = mutable; } + + notifyListeners(); } } @@ -128,10 +138,13 @@ public void clear() { } else { this.backing = null; } + + notifyListeners(); } public void setBacking(@Nullable CyclingList backing) { this.backing = backing; + notifyListeners(); } private static AbstractList getListFromCollection(Collection entries) { @@ -173,4 +186,19 @@ public CyclingList getBacking() { if (this.backing == null) return CyclingList.of(this.empty); return this.backing; } + + public void addListener(Consumer> listener) { + if (this.listeners instanceof ArrayList>> list) { + list.add(listener); + } else { + this.listeners = new ArrayList<>(this.listeners); + this.listeners.add(listener); + } + } + + private void notifyListeners() { + for (Consumer> listener : this.listeners) { + listener.accept(this); + } + } } From b666943c4f14cfc6a8f1db7d3ac7fa2c7c0519da Mon Sep 17 00:00:00 2001 From: shedaniel Date: Tue, 17 Sep 2024 13:53:26 +0800 Subject: [PATCH 4/7] Compute the output of smithing displays on the fly --- .../categories/DefaultSmithingCategory.java | 34 ++++++++- .../displays/DefaultSmithingDisplay.java | 76 ++++++++++++++----- 2 files changed, 89 insertions(+), 21 deletions(-) diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/DefaultSmithingCategory.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/DefaultSmithingCategory.java index 982221600..8ca82cc14 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/DefaultSmithingCategory.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/client/categories/DefaultSmithingCategory.java @@ -27,15 +27,22 @@ import me.shedaniel.math.Point; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.client.gui.Renderer; +import me.shedaniel.rei.api.client.gui.widgets.Slot; import me.shedaniel.rei.api.client.gui.widgets.Widget; import me.shedaniel.rei.api.client.gui.widgets.Widgets; import me.shedaniel.rei.api.client.registry.display.DisplayCategory; import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.display.basic.BasicDisplay; +import me.shedaniel.rei.api.common.entry.EntryIngredient; +import me.shedaniel.rei.api.common.entry.EntryStack; import me.shedaniel.rei.api.common.util.EntryStacks; import me.shedaniel.rei.plugin.common.BuiltinPlugin; import me.shedaniel.rei.plugin.common.displays.DefaultSmithingDisplay; +import net.minecraft.core.RegistryAccess; import net.minecraft.network.chat.Component; import net.minecraft.world.level.block.Blocks; +import org.apache.commons.lang3.mutable.MutableBoolean; +import org.jetbrains.annotations.ApiStatus; import java.util.List; @@ -65,14 +72,23 @@ public List setupDisplay(DefaultSmithingDisplay display, Rectangle bound widgets.add(Widgets.createArrow(new Point(startPoint.x + 27 + offsetX, startPoint.y + 4))); widgets.add(Widgets.createResultSlotBackground(new Point(startPoint.x + 61 + offsetX, startPoint.y + 5))); if (!legacy) { - widgets.add(Widgets.createSlot(new Point(startPoint.x + 4 - 18 * 2 + offsetX, startPoint.y + 5)).entries(display.getInputEntries().get(0)).markInput()); - widgets.add(Widgets.createSlot(new Point(startPoint.x + 4 - 18 + offsetX, startPoint.y + 5)).entries(display.getInputEntries().get(1)).markInput()); - widgets.add(Widgets.createSlot(new Point(startPoint.x + 4 + offsetX, startPoint.y + 5)).entries(display.getInputEntries().get(2)).markInput()); + Slot templateSlot, baseSlot, additionSlot, resultSlot; + MutableBoolean dirty = new MutableBoolean(true); + widgets.add(templateSlot = Widgets.createSlot(new Point(startPoint.x + 4 - 18 * 2 + offsetX, startPoint.y + 5)).entries(display.getInputEntries().get(0)).withEntriesListener(slot -> dirty.setTrue()).markInput()); + widgets.add(baseSlot = Widgets.createSlot(new Point(startPoint.x + 4 - 18 + offsetX, startPoint.y + 5)).entries(display.getInputEntries().get(1)).withEntriesListener(slot -> dirty.setTrue()).markInput()); + widgets.add(additionSlot = Widgets.createSlot(new Point(startPoint.x + 4 + offsetX, startPoint.y + 5)).entries(display.getInputEntries().get(2)).withEntriesListener(slot -> dirty.setTrue()).markInput()); + widgets.add(resultSlot = Widgets.createSlot(new Point(startPoint.x + 61 + offsetX, startPoint.y + 5)).entries(display.getOutputEntries().get(0)).disableBackground().markOutput()); + widgets.add(Widgets.createDrawableWidget((helper, matrices, mouseX, mouseY, delta) -> { + if (dirty.booleanValue()) { + resultSlot.clearEntries().entries(getOutput(display, BasicDisplay.registryAccess(), templateSlot.getCurrentEntry(), baseSlot.getCurrentEntry(), additionSlot.getCurrentEntry())); + dirty.setFalse(); + } + })); } else { widgets.add(Widgets.createSlot(new Point(startPoint.x + 4 - 22 + offsetX, startPoint.y + 5)).entries(display.getInputEntries().get(0)).markInput()); widgets.add(Widgets.createSlot(new Point(startPoint.x + 4 + offsetX, startPoint.y + 5)).entries(display.getInputEntries().get(1)).markInput()); + widgets.add(Widgets.createSlot(new Point(startPoint.x + 61 + offsetX, startPoint.y + 5)).entries(display.getOutputEntries().get(0)).disableBackground().markOutput()); } - widgets.add(Widgets.createSlot(new Point(startPoint.x + 61 + offsetX, startPoint.y + 5)).entries(display.getOutputEntries().get(0)).disableBackground().markOutput()); return widgets; } @@ -80,4 +96,14 @@ public List setupDisplay(DefaultSmithingDisplay display, Rectangle bound public int getDisplayHeight() { return 36; } + + @ApiStatus.Experimental + private static EntryIngredient getOutput(DefaultSmithingDisplay display, RegistryAccess registryAccess, EntryStack template, EntryStack base, EntryStack addition) { + if (display.getType() == DefaultSmithingDisplay.SmithingRecipeType.TRIM) { + EntryIngredient output = DefaultSmithingDisplay.getTrimmingOutput(registryAccess, template, base, addition); + if (!output.isEmpty()) return output; + } + + return display.getOutputEntries().get(0); + } } diff --git a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultSmithingDisplay.java b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultSmithingDisplay.java index 5e2b2e5fc..bd38d81c7 100644 --- a/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultSmithingDisplay.java +++ b/default-plugin/src/main/java/me/shedaniel/rei/plugin/common/displays/DefaultSmithingDisplay.java @@ -26,6 +26,8 @@ import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.display.basic.BasicDisplay; import me.shedaniel.rei.api.common.entry.EntryIngredient; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; import me.shedaniel.rei.api.common.util.EntryIngredients; import me.shedaniel.rei.api.common.util.EntryStacks; import me.shedaniel.rei.plugin.common.BuiltinPlugin; @@ -39,12 +41,16 @@ import net.minecraft.world.item.crafting.SmithingTransformRecipe; import net.minecraft.world.item.crafting.SmithingTrimRecipe; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Optional; public class DefaultSmithingDisplay extends BasicDisplay { + @Nullable + private final SmithingRecipeType type; + public DefaultSmithingDisplay(LegacyUpgradeRecipe recipe) { this( recipe, @@ -93,25 +99,15 @@ public static List from(SmithingTrimRecipe recipe) { .orElse(null); if (trimMaterial == null) continue; - ArmorTrim armorTrim = new ArmorTrim(trimMaterial, trimPattern); - EntryIngredient.Builder baseItems = EntryIngredient.builder(), outputItems = EntryIngredient.builder(); - for (ItemStack item : recipe.base.getItems()) { - Optional trim = ArmorTrim.getTrim(registryAccess, item); - if (trim.isEmpty() || !trim.get().hasPatternAndMaterial(trimPattern, trimMaterial)) { - ItemStack newItem = item.copy(); - newItem.setCount(1); - if (ArmorTrim.setTrim(registryAccess, newItem, armorTrim)) { - baseItems.add(EntryStacks.of(item.copy())); - outputItems.add(EntryStacks.of(newItem)); - } - } - } + EntryIngredient baseIngredient = EntryIngredients.ofIngredient(recipe.base); + EntryIngredient templateOutput = baseIngredient.isEmpty() ? EntryIngredient.empty() + : getTrimmingOutput(registryAccess, EntryStacks.of(templateItem), baseIngredient.get(0), EntryStacks.of(additionStack)); + displays.add(new DefaultSmithingDisplay(List.of( EntryIngredients.of(templateItem), - baseItems.build(), + baseIngredient, EntryIngredients.of(additionStack) - ), List.of(outputItems.build()), - Optional.ofNullable(recipe.getId()))); + ), List.of(templateOutput), SmithingRecipeType.TRIM, Optional.ofNullable(recipe.getId()))); } } return displays; @@ -126,7 +122,13 @@ public DefaultSmithingDisplay(SmithingRecipe recipe, List input } public DefaultSmithingDisplay(List inputs, List outputs, Optional location) { + this(inputs, outputs, null, location); + } + + @ApiStatus.Experimental + public DefaultSmithingDisplay(List inputs, List outputs, @Nullable SmithingRecipeType type, Optional location) { super(inputs, outputs, location); + this.type = type; } @Override @@ -134,7 +136,47 @@ public CategoryIdentifier getCategoryIdentifier() { return BuiltinPlugin.SMITHING; } + @ApiStatus.Experimental + @Nullable + public SmithingRecipeType getType() { + return type; + } + public static BasicDisplay.Serializer serializer() { - return BasicDisplay.Serializer.ofSimple(DefaultSmithingDisplay::new); + return BasicDisplay.Serializer.of((input, output, id, tag) -> { + SmithingRecipeType type = tag.contains("Type") ? SmithingRecipeType.valueOf(tag.getString("Type")) : null; + return new DefaultSmithingDisplay(input, output, type, id); + }, (display, tag) -> { + if (display.type != null) tag.putString("Type", display.type.name()); + }); + } + + @ApiStatus.Experimental + public enum SmithingRecipeType { + TRIM, + TRANSFORM + } + + @ApiStatus.Experimental + @ApiStatus.Internal + public static EntryIngredient getTrimmingOutput(RegistryAccess registryAccess, EntryStack template, EntryStack base, EntryStack addition) { + if (template.getType() != VanillaEntryTypes.ITEM || base.getType() != VanillaEntryTypes.ITEM || addition.getType() != VanillaEntryTypes.ITEM) return EntryIngredient.empty(); + ItemStack templateItem = template.castValue(); + ItemStack baseItem = base.castValue(); + ItemStack additionItem = addition.castValue(); + Holder.Reference trimPattern = TrimPatterns.getFromTemplate(registryAccess, templateItem) + .orElse(null); + if (trimPattern == null) return EntryIngredient.empty(); + Holder.Reference trimMaterial = TrimMaterials.getFromIngredient(registryAccess, additionItem) + .orElse(null); + if (trimMaterial == null) return EntryIngredient.empty(); + ArmorTrim armorTrim = new ArmorTrim(trimMaterial, trimPattern); + Optional trim = ArmorTrim.getTrim(registryAccess, baseItem); + if (trim.isPresent() && trim.get().hasPatternAndMaterial(trimPattern, trimMaterial)) return EntryIngredient.empty(); + ItemStack newItem = baseItem.copy(); + newItem.setCount(1); + if (ArmorTrim.setTrim(registryAccess, newItem, armorTrim)) { + return EntryIngredients.of(newItem); + } else return EntryIngredient.empty(); } } From 644c3f591d42d91c8445b199202b37ba05b0bf93 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Tue, 17 Sep 2024 21:36:09 +0800 Subject: [PATCH 5/7] Use InstanceHelper to get registryAccess --- .../rei/api/common/display/basic/BasicDisplay.java | 9 ++------- api/src/main/java/me/shedaniel/rei/impl/Internals.java | 6 ++++++ .../java/me/shedaniel/rei/RoughlyEnoughItemsCore.java | 4 ++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/me/shedaniel/rei/api/common/display/basic/BasicDisplay.java b/api/src/main/java/me/shedaniel/rei/api/common/display/basic/BasicDisplay.java index 09f913832..1d4ecb392 100644 --- a/api/src/main/java/me/shedaniel/rei/api/common/display/basic/BasicDisplay.java +++ b/api/src/main/java/me/shedaniel/rei/api/common/display/basic/BasicDisplay.java @@ -23,12 +23,11 @@ package me.shedaniel.rei.api.common.display.basic; -import dev.architectury.utils.EnvExecutor; -import dev.architectury.utils.GameInstance; import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.api.common.display.SimpleDisplaySerializer; import me.shedaniel.rei.api.common.entry.EntryIngredient; import me.shedaniel.rei.api.common.util.EntryIngredients; +import me.shedaniel.rei.impl.Internals; import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; @@ -38,16 +37,12 @@ import java.util.List; import java.util.Optional; -import java.util.function.Supplier; /** * A basic implementation of a display, consisting of a list of inputs, a list of outputs * and a possible display location. */ public abstract class BasicDisplay implements Display { - protected static final Supplier REGISTRY_ACCESS = - EnvExecutor.getEnvSpecific(() -> () -> () -> GameInstance.getClient().player.level.registryAccess(), - () -> () -> () -> GameInstance.getServer().registryAccess()); protected List inputs; protected List outputs; protected Optional location; @@ -64,7 +59,7 @@ public BasicDisplay(List inputs, List outputs, @ApiStatus.Experimental public static RegistryAccess registryAccess() { - return REGISTRY_ACCESS.get(); + return Internals.getRegistryAccess(); } /** diff --git a/api/src/main/java/me/shedaniel/rei/impl/Internals.java b/api/src/main/java/me/shedaniel/rei/impl/Internals.java index 112b030ce..bcbfcc7fb 100644 --- a/api/src/main/java/me/shedaniel/rei/impl/Internals.java +++ b/api/src/main/java/me/shedaniel/rei/impl/Internals.java @@ -35,6 +35,7 @@ import me.shedaniel.rei.api.common.plugins.REIServerPlugin; import me.shedaniel.rei.api.common.transfer.info.MenuInfoRegistry; import me.shedaniel.rei.impl.common.InternalLogger; +import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.Tag; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Unit; @@ -55,6 +56,7 @@ public final class Internals { private static Function> categoryIdentifier = (object) -> throwNotSetup(); private static Supplier stubMenuInfoRegistry = Internals::throwNotSetup; private static Supplier logger = Internals::throwNotSetup; + private static Supplier registryAccess = Internals::throwNotSetup; private static T throwNotSetup() { throw new AssertionError("REI Internals have not been initialized!"); @@ -117,6 +119,10 @@ public static InternalLogger getInternalLogger() { return logger.get(); } + public static RegistryAccess getRegistryAccess() { + return registryAccess.get(); + } + public interface EntryStackProvider { EntryStack empty(); diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java index 3f99fa7cb..8f4738fe9 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java @@ -55,8 +55,10 @@ import me.shedaniel.rei.impl.common.registry.RecipeManagerContextImpl; import me.shedaniel.rei.impl.common.transfer.MenuInfoRegistryImpl; import me.shedaniel.rei.impl.common.transfer.SlotAccessorRegistryImpl; +import me.shedaniel.rei.impl.common.util.InstanceHelper; import me.shedaniel.rei.impl.init.PluginDetector; import me.shedaniel.rei.impl.init.PrimitivePlatformAdapter; +import net.minecraft.core.RegistryAccess; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackType; import net.minecraft.util.Unit; @@ -69,6 +71,7 @@ import java.util.ServiceLoader; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import java.util.function.UnaryOperator; @ApiStatus.Internal @@ -126,6 +129,7 @@ public static void attachCommonInternals() { Internals.attachInstanceSupplier(LOGGER, "logger"); CategoryIdentifierImpl.attach(); Internals.attachInstance((Function>) DeferringEntryTypeProviderImpl.INSTANCE, "entryTypeDeferred"); + Internals.attachInstance((Supplier) () -> InstanceHelper.getInstance().registryAccess(), "registryAccess"); Internals.attachInstance(EntryStackProviderImpl.INSTANCE, Internals.EntryStackProvider.class); Internals.attachInstance(NbtHasherProviderImpl.INSTANCE, Internals.NbtHasherProvider.class); Internals.attachInstance(EntryIngredientImpl.INSTANCE, Internals.EntryIngredientProvider.class); From 465d3c1c3bba524bcd7922c9a4236551fd041d99 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Tue, 17 Sep 2024 21:53:41 +0800 Subject: [PATCH 6/7] Add ATs for NeoForge --- neoforge/src/main/resources/META-INF/accesstransformer.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/neoforge/src/main/resources/META-INF/accesstransformer.cfg b/neoforge/src/main/resources/META-INF/accesstransformer.cfg index d3cd4b3a0..bff538931 100644 --- a/neoforge/src/main/resources/META-INF/accesstransformer.cfg +++ b/neoforge/src/main/resources/META-INF/accesstransformer.cfg @@ -48,3 +48,5 @@ public-f net.minecraft.client.gui.font.CodepointMap blockMap # blockMap public-f net.minecraft.client.gui.font.CodepointMap blockConstructor # blockConstructor public net.minecraft.world.item.CreativeModeTab displayItemsGenerator # displayItemsGenerator public net.minecraft.world.item.CreativeModeTab$ItemDisplayBuilder +public net.minecraft.client.multiplayer.ClientLevel connection # connection +public net.minecraft.client.multiplayer.MultiPlayerGameMode connection # connection From 15a46dc3a91cf3f8fdb7a1ddf382ce05bfcf742e Mon Sep 17 00:00:00 2001 From: shedaniel Date: Tue, 17 Sep 2024 22:11:26 +0800 Subject: [PATCH 7/7] Fixes #1724 --- fabric/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fabric/build.gradle b/fabric/build.gradle index 496ea21d4..268eb8ed7 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -35,8 +35,8 @@ dependencies { } //modRuntime("com.terraformersmc:modmenu:${modmenu_version}") { transitive false } modApi("dev.architectury:architectury-fabric:${architectury_version}") - modApi("me.shedaniel:error-notifier-fabric:1.0.9") - include("me.shedaniel:error-notifier-fabric:1.0.9") + modApi("me.shedaniel:error-notifier-fabric:1.0.11") + include("me.shedaniel:error-notifier-fabric:1.0.11") depProjects.forEach { common(project(path: it, configuration: "namedElements")) { transitive false }