From 15ff1dcfe181d3469b5a1d28986cf965f0e95df4 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Wed, 31 May 2023 23:18:02 +0800 Subject: Fix #1396 and fix #998 --- .../rei/RoughlyEnoughItemsCoreClient.java | 2 + .../shedaniel/rei/impl/client/REIRuntimeImpl.java | 3 - .../rei/impl/client/config/ConfigObjectImpl.java | 6 + .../rei/impl/client/gui/widget/EntryWidget.java | 35 ++-- .../registry/display/DisplayRegistryImpl.java | 153 ++-------------- .../client/registry/display/DisplaysHolder.java | 96 ++++++++++ .../registry/display/DisplaysHolderImpl.java | 193 +++++++++++++++++++++ .../impl/client/registry/display/RemappingMap.java | 124 +++++++++++++ .../rei/impl/client/search/SearchRuntime.java | 46 +++++ .../shedaniel/rei/impl/client/view/ViewsImpl.java | 27 ++- .../assets/roughlyenoughitems/lang/en_us.json | 1 + 11 files changed, 526 insertions(+), 160 deletions(-) create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolder.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolderImpl.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/RemappingMap.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/search/SearchRuntime.java (limited to 'runtime/src') diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java index f1b1110e8..46f6589f2 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java @@ -78,6 +78,7 @@ import me.shedaniel.rei.impl.client.registry.category.CategoryRegistryImpl; import me.shedaniel.rei.impl.client.registry.display.DisplayRegistryImpl; import me.shedaniel.rei.impl.client.registry.screen.ScreenRegistryImpl; import me.shedaniel.rei.impl.client.search.SearchProviderImpl; +import me.shedaniel.rei.impl.client.search.SearchRuntime; import me.shedaniel.rei.impl.client.search.method.InputMethodRegistryImpl; import me.shedaniel.rei.impl.client.subsets.SubsetsRegistryImpl; import me.shedaniel.rei.impl.client.transfer.TransferHandlerRegistryImpl; @@ -231,6 +232,7 @@ public class RoughlyEnoughItemsCoreClient { new FavoriteEntryTypeRegistryImpl(), new SubsetsRegistryImpl(), new TransferHandlerRegistryImpl(), + new SearchRuntime(), new REIRuntimeImpl(), new ConfigAddonRegistryImpl()), "clientPluginManager"); } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/REIRuntimeImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/REIRuntimeImpl.java index 2e7018ad8..fad1fdf6c 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/REIRuntimeImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/REIRuntimeImpl.java @@ -47,7 +47,6 @@ import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl; import me.shedaniel.rei.impl.client.gui.hints.HintProvider; import me.shedaniel.rei.impl.client.gui.widget.CachedEntryListRender; import me.shedaniel.rei.impl.client.gui.widget.search.OverlaySearchField; -import me.shedaniel.rei.impl.client.search.argument.Argument; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; @@ -255,7 +254,6 @@ public class REIRuntimeImpl implements REIRuntime { @Override public void startReload() { - Argument.resetCache(false); getOverlay().ifPresent(ScreenOverlay::queueReloadOverlay); lastDisplayScreen.clear(); if (!RenderSystem.isOnRenderThread()) { @@ -272,7 +270,6 @@ public class REIRuntimeImpl implements REIRuntime { @Override public void endReload(ReloadStage stage) { - Argument.resetCache(true); getOverlay().ifPresent(ScreenOverlay::queueReloadOverlay); } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java index f8e1f02d0..47f84a7c4 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java @@ -302,6 +302,11 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData { advanced.miscellaneous.cachingFastEntryRendering = doesCacheEntryRendering; } + @Override + public boolean doesCacheDisplayLookup() { + return advanced.miscellaneous.cachingDisplayLookup; + } + @Override public boolean doDebugRenderTimeRequired() { return advanced.layout.debugRenderTimeRequired; @@ -726,6 +731,7 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData { private boolean newFastEntryRendering = true; @ConfigEntry.Gui.PrefixText private boolean cachingFastEntryRendering = false; + private boolean cachingDisplayLookup = true; } public static class Filtering { 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 7d23a89d7..777b44d06 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 @@ -60,6 +60,8 @@ import me.shedaniel.rei.impl.client.gui.InternalTextures; import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl; import me.shedaniel.rei.impl.client.gui.dragging.CurrentDraggingStack; import me.shedaniel.rei.impl.client.gui.widget.favorites.FavoritesListWidget; +import me.shedaniel.rei.impl.client.registry.display.DisplayRegistryImpl; +import me.shedaniel.rei.impl.client.registry.display.DisplaysHolder; import me.shedaniel.rei.impl.client.view.ViewsImpl; import net.minecraft.ChatFormatting; import net.minecraft.CrashReport; @@ -317,22 +319,24 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { try { DisplayRegistry displayRegistry = DisplayRegistry.getInstance(); + DisplaysHolder displaysHolder = ((DisplayRegistryImpl) displayRegistry).displaysHolder(); CategoryRegistry categoryRegistry = CategoryRegistry.getInstance(); Map, Boolean> filteringQuickCraftCategories = ConfigObject.getInstance().getFilteringQuickCraftCategories(); - for (Map.Entry, List> entry : displayRegistry.getAll().entrySet()) { + boolean shouldFilterDisplays = ConfigObject.getInstance().shouldFilterDisplays(); + + for (Display display : displaysHolder.getAllDisplaysByOutputs(getEntries())) { + CategoryIdentifier categoryIdentifier = display.getCategoryIdentifier(); Optional> configuration; - if ((configuration = categoryRegistry.tryGet(entry.getKey())).isEmpty() - || categoryRegistry.isCategoryInvisible(configuration.get().getCategory())) continue; - if (!filteringQuickCraftCategories.getOrDefault(entry.getKey(), configuration.get().isQuickCraftingEnabledByDefault())) continue; - for (Display display : entry.getValue()) { - if ((!ConfigObject.getInstance().shouldFilterDisplays() || displayRegistry.isDisplayVisible(display)) - && ViewsImpl.isRecipesFor(getEntries(), display)) { - AutoCraftingEvaluator.AutoCraftingResult result = AutoCraftingEvaluator.evaluateAutoCrafting(false, false, display, null); - if (result.successful) { - this.display = display; - this.displayTooltipComponent = Suppliers.memoize(() -> new DisplayTooltipComponent(display)); - return result.successfulHandler; - } + if ((configuration = categoryRegistry.tryGet(categoryIdentifier)).isEmpty() + || categoryRegistry.isCategoryInvisible(configuration.get().getCategory())) continue; + if (!filteringQuickCraftCategories.getOrDefault(categoryIdentifier, configuration.get().isQuickCraftingEnabledByDefault())) + continue; + if ((!shouldFilterDisplays || displayRegistry.isDisplayVisible(display))) { + AutoCraftingEvaluator.AutoCraftingResult result = AutoCraftingEvaluator.evaluateAutoCrafting(false, false, display, null); + if (result.successful) { + this.display = display; + this.displayTooltipComponent = Suppliers.memoize(() -> new DisplayTooltipComponent(display)); + return result.successfulHandler; } } } @@ -351,7 +355,7 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { } if (display != null) { - if (ViewsImpl.isRecipesFor(getEntries(), display)) { + if (ViewsImpl.isRecipesFor(null, getEntries(), display)) { AutoCraftingEvaluator.AutoCraftingResult result = AutoCraftingEvaluator.evaluateAutoCrafting(false, false, display, null); if (result.successful) { return result.successfulHandler; @@ -428,7 +432,8 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { } } - protected void drawExtra(PoseStack matrices, int mouseX, int mouseY, float delta) {} + protected void drawExtra(PoseStack matrices, int mouseX, int mouseY, float delta) { + } @Override @Nullable 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 b875b8530..1aff7f720 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 @@ -24,10 +24,6 @@ package me.shedaniel.rei.impl.client.registry.display; import com.google.common.base.Preconditions; -import com.google.common.collect.ForwardingMap; -import com.google.common.collect.ForwardingMapEntry; -import com.google.common.collect.Iterators; -import com.google.common.collect.Sets; import dev.architectury.event.EventResult; import me.shedaniel.rei.api.client.plugins.REIClientPlugin; import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; @@ -43,9 +39,7 @@ import me.shedaniel.rei.api.common.plugins.PluginManager; import me.shedaniel.rei.impl.common.InternalLogger; import me.shedaniel.rei.impl.common.registry.RecipeManagerContextImpl; import net.minecraft.world.item.crafting.Recipe; -import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.mutable.MutableLong; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -53,127 +47,17 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; public class DisplayRegistryImpl extends RecipeManagerContextImpl implements DisplayRegistry { - private final WeakHashMap displaysBase = new WeakHashMap<>(); - private final Map, DisplaysList> displays = new ConcurrentHashMap<>(); - private final Map, List> unmodifiableDisplays; private final Map, List>> displayGenerators = new ConcurrentHashMap<>(); private final List> globalDisplayGenerators = new ArrayList<>(); private final List visibilityPredicates = new ArrayList<>(); private final List> fillers = new ArrayList<>(); - private final MutableInt displayCount = new MutableInt(0); + private final MutableLong lastAddWarning = new MutableLong(-1); + private DisplaysHolder displaysHolder = new DisplaysHolderImpl(false); public DisplayRegistryImpl() { super(RecipeManagerContextImpl.supplier()); - - this.unmodifiableDisplays = new RemappingMap<>(Collections.unmodifiableMap(displays), list -> { - if (list == null) { - return null; - } else { - return ((DisplaysList) list).unmodifiableList; - } - }, key -> CategoryRegistry.getInstance().tryGet(key).isPresent()); - } - - private static class RemappingMap extends ForwardingMap { - protected final Map map; - protected final UnaryOperator remapper; - protected final Predicate keyPredicate; - - public RemappingMap(Map map, UnaryOperator remapper, Predicate keyPredicate) { - this.map = map; - this.remapper = remapper; - this.keyPredicate = keyPredicate; - } - - @Override - @NotNull - protected Map delegate() { - return map; - } - - @Override - public V get(Object key) { - if (keyPredicate.test((K) key)) { - return remapper.apply(super.get(key)); - } else { - return null; - } - } - - @Override - public boolean containsKey(@Nullable Object key) { - return super.containsKey(key) && keyPredicate.test((K) key); - } - - @Override - public Set keySet() { - return Sets.filter(super.keySet(), keyPredicate::test); - } - - @SuppressWarnings("UnstableApiUsage") - @Override - @NotNull - public Set> entrySet() { - return this.new StandardEntrySet() { - @Override - public Iterator> iterator() { - return mapIterator(map.entrySet().iterator()); - } - }; - } - - @Override - public int size() { - return keySet().size(); - } - - @Override - public Collection values() { - return new AbstractCollection() { - @Override - public Iterator iterator() { - return Iterators.transform(entrySet().iterator(), Entry::getValue); - } - - @Override - public int size() { - return RemappingMap.this.size(); - } - }; - } - - private Iterator> mapIterator(Iterator> iterator) { - return Iterators.transform(Iterators.filter(iterator, entry -> this.keyPredicate.test(entry.getKey())), - this::mapEntry); - } - - private Entry mapEntry(Entry entry) { - return new ForwardingMapEntry<>() { - @Override - @NotNull - protected Entry delegate() { - return entry; - } - - @Override - public V getValue() { - return remapper.apply(entry.getValue()); - } - }; - } - } - - private static class DisplaysList extends ArrayList { - private final List unmodifiableList; - private final List synchronizedList; - - public DisplaysList() { - this.synchronizedList = Collections.synchronizedList(this); - this.unmodifiableList = Collections.unmodifiableList(synchronizedList); - } } @Override @@ -183,11 +67,9 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl new DisplaysList()) - .add(display); - displayCount.increment(); - if (origin != null) { - synchronized (displaysBase) { - displaysBase.put(display, origin); - } - } + this.displaysHolder.add(display, origin); } @Override public Map, List> getAll() { - return unmodifiableDisplays; + return this.displaysHolder.get(); } @Override @@ -287,11 +162,10 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl identifier : displays.keySet()) { + for (CategoryIdentifier identifier : getAll().keySet()) { if (CategoryRegistry.getInstance().tryGet(identifier).isEmpty()) { InternalLogger.getInstance().error("Found displays registered for unknown registry", new IllegalStateException(identifier.toString())); } } - InternalLogger.getInstance().debug("Registered %d displays", displayCount.getValue()); + this.displaysHolder.endReload(); + + InternalLogger.getInstance().debug("Registered %d displays", displaySize()); + } + + public DisplaysHolder displaysHolder() { + return displaysHolder; } @Override @@ -349,12 +229,13 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl( BiPredicate predicate, Function mappingFunction - ) {} + ) { + } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolder.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolder.java new file mode 100644 index 000000000..07b5879cf --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolder.java @@ -0,0 +1,96 @@ +/* + * 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.registry.display; + +import com.google.common.collect.Iterables; +import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.util.CollectionUtils; +import me.shedaniel.rei.impl.client.view.ViewsImpl; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface DisplaysHolder { + boolean doesCache(); + + void add(Display display, @Nullable Object origin); + + int size(); + + Map, List> get(); + + @Nullable + Object getDisplayOrigin(Display display); + + void endReload(); + + boolean isCached(Display display); + + Set getDisplaysNotCached(); + + Set getDisplaysByInput(EntryStack stack); + + Set getDisplaysByOutput(EntryStack stack); + + default Iterable getAllDisplaysByInputs(List> stacks) { + if (stacks.isEmpty()) return List.of(); + Iterable inputCached = null; + if (doesCache()) { + for (EntryStack stack : stacks) { + Set set = getDisplaysByInput(stack); + inputCached = inputCached == null ? set : Iterables.concat(inputCached, set); + } + if (stacks.size() > 1) inputCached = CollectionUtils.distinctReferenceOf(inputCached); + } + Collection notCached = this.getDisplaysNotCached(); + if (notCached.isEmpty()) return inputCached == null ? List.of() : inputCached; + Iterable filteredNotCached = Iterables.filter(notCached, display -> + ViewsImpl.isUsagesFor(null, stacks, display)); + if (inputCached == null) return filteredNotCached; + return Iterables.concat(inputCached, filteredNotCached); + } + + default Iterable getAllDisplaysByOutputs(List> stacks) { + if (stacks.isEmpty()) return List.of(); + Iterable outputCached = null; + if (doesCache()) { + for (EntryStack stack : stacks) { + Set set = getDisplaysByOutput(stack); + outputCached = outputCached == null ? set : Iterables.concat(outputCached, set); + } + if (stacks.size() > 1) outputCached = CollectionUtils.distinctReferenceOf(outputCached); + } + Collection notCached = this.getDisplaysNotCached(); + if (notCached.isEmpty()) return outputCached == null ? List.of() : outputCached; + Iterable filteredNotCached = Iterables.filter(notCached, display -> + ViewsImpl.isRecipesFor(null, stacks, display)); + if (outputCached == null) return filteredNotCached; + return Iterables.concat(outputCached, filteredNotCached); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolderImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolderImpl.java new file mode 100644 index 000000000..f1f930639 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolderImpl.java @@ -0,0 +1,193 @@ +/* + * 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.registry.display; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.Multimaps; +import com.google.common.collect.SetMultimap; +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import me.shedaniel.rei.api.client.config.ConfigObject; +import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; +import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.display.Display; +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.impl.common.InternalLogger; +import org.apache.commons.lang3.mutable.MutableInt; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class DisplaysHolderImpl implements DisplaysHolder { + private final boolean cache; + private final Map, DisplaysList> displays = new ConcurrentHashMap<>(); + private final Map, List> unmodifiableDisplays; + private final WeakHashMap displaysBase = new WeakHashMap<>(); + private Set displaysCached = new ReferenceOpenHashSet<>(); + private Set displaysNotCached = Collections.synchronizedSet(new ReferenceOpenHashSet<>()); + private boolean preprocessed = false; + private SetMultimap, Display> displaysByInput; + private SetMultimap, Display> displaysByOutput; + private final MutableInt displayCount = new MutableInt(0); + + public DisplaysHolderImpl(boolean init) { + this.cache = init && ConfigObject.getInstance().doesCacheDisplayLookup(); + this.unmodifiableDisplays = new RemappingMap<>(Collections.unmodifiableMap(displays), list -> { + if (list == null) { + return null; + } else { + return ((DisplaysList) list).unmodifiableList; + } + }, key -> CategoryRegistry.getInstance().tryGet(key).isPresent()); + this.displaysByInput = createSetMultimap(); + this.displaysByOutput = createSetMultimap(); + } + + @Override + public boolean doesCache() { + return this.cache; + } + + @Override + public void add(Display display, @Nullable Object origin) { + this.displays.computeIfAbsent(display.getCategoryIdentifier(), location -> new DisplaysList()) + .add(display); + this.displayCount.increment(); + if (origin != null) { + synchronized (this.displaysBase) { + this.displaysBase.put(display, origin); + } + } + if (this.cache) { + if (!preprocessed) { + this.displaysNotCached.add(display); + } else { + this.process(display); + this.displaysCached.add(display); + } + } else { + this.displaysNotCached.add(display); + } + } + + @Override + public int size() { + return this.displayCount.intValue(); + } + + @Override + public Map, List> get() { + return this.unmodifiableDisplays; + } + + @Override + public void endReload() { + if (this.cache) { + InternalLogger.getInstance().debug("Processing %d displays for optimal lookup performance...", this.size()); + Stopwatch stopwatch = Stopwatch.createStarted(); + this.displaysCached = new ReferenceOpenHashSet<>(this.size()); + this.displaysByInput = createSetMultimap(); + this.displaysByOutput = createSetMultimap(); + for (Display display : this.displaysNotCached) { + this.process(display); + } + this.displaysCached.addAll(this.displaysNotCached); + this.displaysNotCached = Set.of(); + this.preprocessed = true; + InternalLogger.getInstance().debug("Processed displays for optimal lookup performance in %s.", stopwatch.stop()); + } + } + + private void process(Display display) { + for (EntryIngredient input : display.getInputEntries()) { + for (EntryStack stack : input) { + this.displaysByInput.put(stack, display); + } + } + for (EntryIngredient output : display.getOutputEntries()) { + for (EntryStack stack : output) { + this.displaysByOutput.put(stack, display); + } + } + } + + @Override + public boolean isCached(Display display) { + return this.cache && this.displaysCached.contains(display); + } + + @Override + public Set getDisplaysNotCached() { + return this.displaysNotCached; + } + + @Override + public Set getDisplaysByInput(EntryStack stack) { + return this.displaysByInput.get(stack); + } + + @Override + public Set getDisplaysByOutput(EntryStack stack) { + return this.displaysByOutput.get(stack); + } + + @Override + @Nullable + public Object getDisplayOrigin(Display display) { + synchronized (this.displaysBase) { + return this.displaysBase.get(display); + } + } + + private static class DisplaysList extends ArrayList { + private final List unmodifiableList; + private final List synchronizedList; + + public DisplaysList() { + this.synchronizedList = Collections.synchronizedList(this); + this.unmodifiableList = Collections.unmodifiableList(synchronizedList); + } + } + + private SetMultimap, Display> createSetMultimap() { + return Multimaps.newSetMultimap( + new Object2ObjectOpenCustomHashMap<>(Math.max(10000, this.size() * 5 / 2), new Hash.Strategy<>() { + @Override + public int hashCode(EntryStack stack) { + return Long.hashCode(EntryStacks.hashFuzzy(stack)); + } + + @Override + public boolean equals(EntryStack o1, EntryStack o2) { + return EntryStacks.equalsFuzzy(o1, o2); + } + }), + ReferenceOpenHashSet::new + ); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/RemappingMap.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/RemappingMap.java new file mode 100644 index 000000000..b74d7e642 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/RemappingMap.java @@ -0,0 +1,124 @@ +/* + * 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.registry.display; + +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ForwardingMapEntry; +import com.google.common.collect.Iterators; +import com.google.common.collect.Sets; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +class RemappingMap extends ForwardingMap { + protected final Map map; + protected final UnaryOperator remapper; + protected final Predicate keyPredicate; + + public RemappingMap(Map map, UnaryOperator remapper, Predicate keyPredicate) { + this.map = map; + this.remapper = remapper; + this.keyPredicate = keyPredicate; + } + + @Override + @NotNull + protected Map delegate() { + return map; + } + + @Override + public V get(Object key) { + if (keyPredicate.test((K) key)) { + return remapper.apply(super.get(key)); + } else { + return null; + } + } + + @Override + public boolean containsKey(@Nullable Object key) { + return super.containsKey(key) && keyPredicate.test((K) key); + } + + @Override + public Set keySet() { + return Sets.filter(super.keySet(), keyPredicate::test); + } + + @SuppressWarnings("UnstableApiUsage") + @Override + @NotNull + public Set> entrySet() { + return this.new StandardEntrySet() { + @Override + public Iterator> iterator() { + return mapIterator(map.entrySet().iterator()); + } + }; + } + + @Override + public int size() { + return keySet().size(); + } + + @Override + public Collection values() { + return new AbstractCollection() { + @Override + public Iterator iterator() { + return Iterators.transform(entrySet().iterator(), Entry::getValue); + } + + @Override + public int size() { + return RemappingMap.this.size(); + } + }; + } + + private Iterator> mapIterator(Iterator> iterator) { + return Iterators.transform(Iterators.filter(iterator, entry -> this.keyPredicate.test(entry.getKey())), + this::mapEntry); + } + + private Entry mapEntry(Entry entry) { + return new ForwardingMapEntry<>() { + @Override + @NotNull + protected Entry delegate() { + return entry; + } + + @Override + public V getValue() { + return remapper.apply(entry.getValue()); + } + }; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/SearchRuntime.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/SearchRuntime.java new file mode 100644 index 000000000..337e3261a --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/SearchRuntime.java @@ -0,0 +1,46 @@ +/* + * 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.search; + +import me.shedaniel.rei.api.client.plugins.REIClientPlugin; +import me.shedaniel.rei.api.common.registry.ReloadStage; +import me.shedaniel.rei.api.common.registry.Reloadable; +import me.shedaniel.rei.impl.client.search.argument.Argument; + +public class SearchRuntime implements Reloadable { + @Override + public void startReload() { + Argument.resetCache(false); + } + + @Override + public void startReload(ReloadStage stage) { + this.startReload(); + } + + @Override + public void endReload(ReloadStage stage) { + Argument.resetCache(true); + } +} 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 296a8702f..97bb8ef83 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 @@ -55,6 +55,8 @@ import me.shedaniel.rei.api.common.util.EntryIngredients; import me.shedaniel.rei.api.common.util.EntryStacks; import me.shedaniel.rei.impl.client.gui.craftable.CraftableFilter; import me.shedaniel.rei.impl.client.gui.widget.AutoCraftingEvaluator; +import me.shedaniel.rei.impl.client.registry.display.DisplayRegistryImpl; +import me.shedaniel.rei.impl.client.registry.display.DisplaysHolder; import me.shedaniel.rei.impl.client.util.CrashReportUtils; import me.shedaniel.rei.impl.common.InternalLogger; import me.shedaniel.rei.impl.display.DisplaySpec; @@ -113,6 +115,7 @@ public class ViewsImpl implements Views { List> recipesForStacksWildcard = CollectionUtils.flatMap(recipesForStacks, wildcardFunction); List> usagesForStacksWildcard = CollectionUtils.flatMap(usagesForStacks, wildcardFunction); DisplayRegistry displayRegistry = DisplayRegistry.getInstance(); + DisplaysHolder displaysHolder = ((DisplayRegistryImpl) displayRegistry).displaysHolder(); Map, List> result = Maps.newLinkedHashMap(); for (CategoryRegistry.CategoryConfiguration categoryConfiguration : CategoryRegistry.getInstance()) { @@ -137,13 +140,13 @@ public class ViewsImpl implements Views { for (Display display : allRecipesFromCategory) { if (processingVisibilityHandlers && !displayRegistry.isDisplayVisible(display)) continue; if (!recipesForStacks.isEmpty()) { - if (isRecipesFor(recipesForStacks, display)) { + if (isRecipesFor(displaysHolder, recipesForStacks, display)) { set.add(display); continue; } } if (!usagesForStacks.isEmpty()) { - if (isUsagesFor(usagesForStacks, display)) { + if (isUsagesFor(displaysHolder, usagesForStacks, display)) { set.add(display); continue; } @@ -153,13 +156,13 @@ public class ViewsImpl implements Views { for (Display display : allRecipesFromCategory) { if (processingVisibilityHandlers && !displayRegistry.isDisplayVisible(display)) continue; if (!recipesForStacksWildcard.isEmpty()) { - if (isRecipesFor(recipesForStacksWildcard, display)) { + if (isRecipesFor(displaysHolder, recipesForStacksWildcard, display)) { set.add(display); continue; } } if (!usagesForStacksWildcard.isEmpty()) { - if (isUsagesFor(usagesForStacksWildcard, display)) { + if (isUsagesFor(displaysHolder, usagesForStacksWildcard, display)) { set.add(display); continue; } @@ -290,11 +293,23 @@ public class ViewsImpl implements Views { return resultSpeced; } - public static boolean isRecipesFor(List> stacks, Display display) { + public static boolean isRecipesFor(@Nullable DisplaysHolder displaysHolder, List> stacks, Display display) { + if (displaysHolder != null && displaysHolder.isCached(display)) { + for (EntryStack recipesFor : stacks) { + return displaysHolder.getDisplaysByOutput(recipesFor).contains(display); + } + } + return checkUsages(stacks, display, display.getOutputEntries()); } - public static boolean isUsagesFor(List> stacks, Display display) { + public static boolean isUsagesFor(@Nullable DisplaysHolder displaysHolder, List> stacks, Display display) { + if (displaysHolder != null && displaysHolder.isCached(display)) { + for (EntryStack recipesFor : stacks) { + return displaysHolder.getDisplaysByInput(recipesFor).contains(display); + } + } + return checkUsages(stacks, display, display.getInputEntries()); } 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 35d9f23d0..1e3b150fa 100755 --- a/runtime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json +++ b/runtime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json @@ -322,6 +322,7 @@ "config.roughlyenoughitems.miscellaneous.newFastEntryRendering": "Faster Entry Rendering:", "config.roughlyenoughitems.miscellaneous.cachingFastEntryRendering": "Caching Entry Rendering:", "config.roughlyenoughitems.miscellaneous.cachingFastEntryRendering.@PrefixText": "This option will improve performance for rendering entries for up to 5x, with an average of 2.5x performance gain. However, this will break enchantment glints and animated textures.", + "config.roughlyenoughitems.miscellaneous.cachingDisplayLookup": "Caching Display Lookup:", "config.roughlyenoughitems.itemCheatingMode": "Item Cheating Amount:", "config.roughlyenoughitems.itemCheatingMode.rei_like": "Normal", "config.roughlyenoughitems.itemCheatingMode.jei_like": "Inverted", -- cgit